therselman
8 years ago
committed by
GitHub
12 changed files with 800 additions and 11 deletions
@ -1,11 +1,11 @@
@@ -1,11 +1,11 @@
|
||||
<?php |
||||
|
||||
namespace Twister; |
||||
|
||||
class App |
||||
{ |
||||
function run() |
||||
{ |
||||
echo 'Hello World'; |
||||
} |
||||
} |
||||
<?php |
||||
|
||||
namespace Twister; |
||||
|
||||
class App |
||||
{ |
||||
function run() |
||||
{ |
||||
echo 'Hello World'; |
||||
} |
||||
} |
||||
|
@ -0,0 +1,80 @@
@@ -0,0 +1,80 @@
|
||||
<?php |
||||
|
||||
namespace Twister; |
||||
|
||||
class Collection implements \Iterator, \Countable, \ArrayAccess |
||||
{ |
||||
protected $_ = null; |
||||
|
||||
public function __construct(array $members = []) |
||||
{ |
||||
$this->_ =& $members; |
||||
} |
||||
|
||||
// |
||||
// Workaround for the `array access functions` eg. array_push($obj->toArray(), 'Hello World!'); |
||||
// |
||||
public function &toArray() |
||||
{ |
||||
return $this->_; |
||||
} |
||||
public function &__invoke() |
||||
{ |
||||
return $this->_; |
||||
} |
||||
|
||||
|
||||
// |
||||
// Iterator interface |
||||
// |
||||
public function rewind() |
||||
{ |
||||
return reset($this->_); |
||||
} |
||||
public function current() |
||||
{ |
||||
return current($this->_); |
||||
} |
||||
public function key() |
||||
{ |
||||
return key($this->_); |
||||
} |
||||
public function next() |
||||
{ |
||||
return next($this->_); |
||||
} |
||||
public function valid() |
||||
{ |
||||
return key($this->_) !== null; |
||||
} |
||||
|
||||
|
||||
// |
||||
// Countable interface |
||||
// |
||||
public function count() |
||||
{ |
||||
return count($this->_); |
||||
} |
||||
|
||||
|
||||
// |
||||
// ArrayAccess interface |
||||
// |
||||
public function offsetSet($id, $value) // eg. $obj['two'] = 'A value'; |
||||
{ |
||||
$this->_[$id] = $value; |
||||
} |
||||
public function offsetExists($id) // eg. isset($obj['two']) |
||||
{ |
||||
return isset($this->_[$id]); |
||||
} |
||||
public function offsetUnset($id) // eg. unset($obj['two']); |
||||
{ |
||||
unset($this->_[$id]); |
||||
} |
||||
public function offsetGet($id) // eg. var_dump($obj['two']); |
||||
{ |
||||
return $this->_[$id]; |
||||
} |
||||
} |
@ -0,0 +1,135 @@
@@ -0,0 +1,135 @@
|
||||
<?php |
||||
|
||||
namespace Twister; |
||||
|
||||
class Container implements ContainerInterface |
||||
{ |
||||
protected $_container = []; |
||||
|
||||
function __set($name, $value) |
||||
{ |
||||
if (isset($this->_container[$name])) |
||||
{ |
||||
$trace = debug_backtrace(); |
||||
trigger_error(__CLASS__ . " container property `{$name}` has already been set and cannot be changed in {$trace[0]['file']} on line {$trace[0]['line']}. Please unset() and re-set the value!", E_USER_ERROR); |
||||
} |
||||
else |
||||
$this->_container[$name] = $value; |
||||
} |
||||
|
||||
function &__get($name) |
||||
{ |
||||
if (isset($this->_container[$name])) |
||||
return $this->_container[$name]; |
||||
|
||||
// Examples of official error messages |
||||
// Notice: Undefined index: muscle in C:\...\app.php on line 34 |
||||
// Undefined property: app::$conf in <b>C:\...\app.php</b> on line <b>111</b><br /> |
||||
$trace = debug_backtrace(); |
||||
trigger_error('Undefined container property: ' . __CLASS__ . "->{$name} in <b>{$trace[0]['file']}</b> on line <b>{$trace[0]['line']}</b>; thrown", E_USER_ERROR); |
||||
return null; |
||||
} |
||||
|
||||
function __isset($name) |
||||
{ |
||||
return isset($this->_container[$name]); |
||||
} |
||||
|
||||
function __unset($name) |
||||
{ |
||||
unset($this->_container[$name]); |
||||
} |
||||
|
||||
function &__call($method, $args) |
||||
{ |
||||
if (isset($this->_container[$method])) |
||||
if (is_callable($this->_container[$method])) |
||||
{ |
||||
array_unshift($args, $this); // Prepend $this container to the beginning of the function call arguments! |
||||
$result = call_user_func_array($this->_container[$method], $args); |
||||
return $result; |
||||
} |
||||
else |
||||
return $this->_container[$method]; |
||||
else |
||||
{ |
||||
if (preg_match('/^([gs]et|has|isset|unset)([A-Z])(.*)$/', $method, $match)) |
||||
{ |
||||
$property = strtolower($match[2]). $match[3]; |
||||
if (isset($this->_container[$property])) |
||||
{ |
||||
switch($match[1]) |
||||
{ |
||||
case 'get': return $this->_container[$property]; |
||||
case 'set': return $this->_container[$property] = $args[0]; |
||||
case 'has': // fallthrough vvv alias for `isset` |
||||
case 'isset': $result = isset($this->_container[$property]); return $result; |
||||
case 'unset': $result = null; unset($this->_container[$property]); return $result; |
||||
} |
||||
} |
||||
throw new \InvalidArgumentException("Property {$property} doesn't exist"); |
||||
} |
||||
throw new \InvalidArgumentException(__CLASS__ . "->{$method}() doesn't exist"); |
||||
} |
||||
|
||||
|
||||
// old method |
||||
if (isset(self::$_[$method]) && is_callable(self::$_[$method])) |
||||
return call_user_func_array(self::$_[$method], $args); |
||||
if ( ! empty($args)) |
||||
self::$_[$method] = $args[0]; |
||||
return self::$_[$method]; |
||||
/* // old method |
||||
if ( ! empty($args)) |
||||
self::$_[$method] = $args[0]; |
||||
if (isset(self::$_[$method])) |
||||
return self::$_[$method]; |
||||
else |
||||
throw new InvalidArgumentException("_::{$method}() doesn't exist"); |
||||
/* |
||||
Taken from: https://stackoverflow.com/questions/1279382/magic-get-getter-for-static-properties-in-php |
||||
static public function __callStatic($method, $args) |
||||
{ |
||||
if (preg_match('/^([gs]et)([A-Z])(.*)$/', $method, $match)) |
||||
{ |
||||
$reflector = new \ReflectionClass(__CLASS__); |
||||
$property = strtolower($match[2]). $match[3]; |
||||
if ($reflector->hasProperty($property)) |
||||
{ |
||||
$property = $reflector->getProperty($property); |
||||
switch($match[1]) |
||||
{ |
||||
case 'get': return self::${$property->name}; |
||||
case 'set': return self::${$property->name} = $args[0]; |
||||
} |
||||
} |
||||
else throw new InvalidArgumentException("Property {$property} doesn't exist"); |
||||
} |
||||
} |
||||
*/ |
||||
} |
||||
|
||||
|
||||
function set($key, $value) // alias for __set() |
||||
{ |
||||
return $this->_container[$key] =& $value; |
||||
} |
||||
function &get($key) // alias for __get() |
||||
{ |
||||
return $this->_container[$key]; |
||||
} |
||||
function has($key) // alias for __isset() |
||||
{ |
||||
return isset($this->_container[$key]); |
||||
} |
||||
function &merge($key, array $arr) // we need this function because we cannot (re)`set` the arrays to new values without unsetting the old values first! ie. __set() will fail because it already exists! |
||||
{ |
||||
// TODO: Add is_array() checks to the container, and add variable number of array inputs! |
||||
$this->_container[$key] = array_merge($this->_container[$key], $arr); |
||||
return $this->_container[$key]; |
||||
} |
||||
function remove($key) // alias for __unset() |
||||
{ |
||||
unset($this->_container[$key]); |
||||
} |
||||
} |
@ -0,0 +1,21 @@
@@ -0,0 +1,21 @@
|
||||
<?php |
||||
|
||||
namespace Twister; |
||||
|
||||
trait ContainerAwareTrait |
||||
{ |
||||
/** |
||||
* @var ContainerInterface |
||||
*/ |
||||
protected $container; |
||||
|
||||
/** |
||||
* Sets the container. |
||||
* |
||||
* @param ContainerInterface|null $container A ContainerInterface instance or null |
||||
*/ |
||||
public function setContainer(ContainerInterface $container = null) |
||||
{ |
||||
$this->container = $container; |
||||
} |
||||
} |
@ -0,0 +1,18 @@
@@ -0,0 +1,18 @@
|
||||
<?php |
||||
|
||||
namespace Twister; |
||||
|
||||
interface ContainerInterface |
||||
{ |
||||
function __set($name, $value); |
||||
function &__get($name); |
||||
function __isset($name); |
||||
function __unset($name); |
||||
function &__call($method, $args); |
||||
|
||||
function set($key, $value); |
||||
function &get($key); |
||||
function has($key); |
||||
function &merge($key, array $arr); |
||||
function remove($key); |
||||
} |
@ -0,0 +1,23 @@
@@ -0,0 +1,23 @@
|
||||
<?php |
||||
|
||||
namespace Twister; |
||||
|
||||
abstract class Controller implements ContainerInterface |
||||
{ |
||||
use ContainerAwareTrait; |
||||
|
||||
/** |
||||
* ContainerAwareTrait |
||||
*/ |
||||
function __set($name, $value) { return $this->container->__set($name, $value); } |
||||
function &__get($name) { return $this->container->__get($name); } |
||||
function __isset($name) { return $this->container->__isset($name); } |
||||
function __unset($name) { return $this->container->__unset($name); } |
||||
function &__call($method, $args) { return $this->container->__call($method, $args); } |
||||
|
||||
function set($key, $value) { return $this->container->set($key, $value); } |
||||
function &get($key) { return $this->container->get($key); } |
||||
function has($key) { return $this->container->has($key); } |
||||
function &merge($key, array $arr) { return $this->container->merge($key, $arr); } |
||||
function remove($key) { return $this->container->remove($key); } |
||||
} |
@ -0,0 +1,286 @@
@@ -0,0 +1,286 @@
|
||||
<?php |
||||
|
||||
namespace Twister; |
||||
|
||||
class Db extends MySQLi |
||||
{ |
||||
static function build($module, array $commands = array()) |
||||
{ |
||||
static $_fn = null; |
||||
if ( ! isset($_fn[$module])) |
||||
$_fn[$module] = require __DIR__ . '/../queries/' . $module . '.php'; |
||||
return $_fn[$module]($commands); |
||||
} |
||||
// helper functions for the build scripts! |
||||
private static function preg_replace_query($sql, $values) |
||||
{ |
||||
return preg_replace_callback('~{(\w+)(?::(.*))?}~U', function ($matches) use ($values) { return isset($values[$matches[1]]) ? $values[$matches[1]] : (isset($matches[2]) ? $matches[2] : null); }, $sql); |
||||
} |
||||
private static function get_context_fields(&$fields, &$contexts, $context) |
||||
{ |
||||
if (is_string($contexts[$context])) |
||||
$contexts[$context] = array_flip(preg_split('/[\s,]+/', $contexts[$context])); |
||||
return implode(', ', array_intersect_key($fields, $contexts[$context])); |
||||
} |
||||
private static function get_fields(&$fields, $list) |
||||
{ // eg. db::get_fields(['id' => 'u.id', 'name' => 'u.name'], 'id, name'); ... result = 'u.id, u.name' |
||||
return implode(', ', array_intersect_key($fields, array_flip(preg_split('/[\s,]+/', $list)))); |
||||
} |
||||
|
||||
function set_time_zone($time_zone, $lc_time_names = null) |
||||
{ |
||||
$this->real_query('SET time_zone = "' . $time_zone . '"' . $lc_time_names ? ', lc_time_names = "' . $lc_time_names . '"' : null); |
||||
} |
||||
|
||||
|
||||
// function written on 24 May 2017 @ 12pm |
||||
// I realised that fetch_object() was not powerful or flexible enough to create custom objects. |
||||
// So with this function, I can initialize an object with custom parameters, or anything else I want! |
||||
// I can return an array of objects, or discard the result completely. |
||||
// eg. $list = \db::fetch_callback('SELECT * FROM worlds', function (&$row, &$result) { $result[] = $row['id']; /* (optional) return true; */ }); |
||||
// optionally `return false;` in callback to stop processing |
||||
function fetch_callback($sql, callable $callback) |
||||
{ |
||||
$result = null; |
||||
$rs = $this->query($sql, MYSQLI_USE_RESULT); |
||||
while ($row = $rs->fetch_assoc()) |
||||
{ |
||||
// if (call_user_func_array($callback, [&$row, &$result]) === false) // alternative: when using references, the values MUST be passed via an array with call_user_func_array(), call_user_func() only passes by value! |
||||
if ($callback($row, $result) === false) |
||||
break; |
||||
} |
||||
$rs->free_result(); |
||||
return $result; |
||||
} |
||||
|
||||
function get_array($sql, $indexes = null, $values = null) |
||||
{ |
||||
$rs = $this->query($sql, MYSQLI_USE_RESULT); // MYSQLI_USE_RESULT was about 12% faster than MYSQLI_STORE_RESULT |
||||
if (isset($indexes)) |
||||
{ |
||||
if ($indexes == '#') // used in the ajax queries where we need to `override` the two field array index => value (field_count === 2) below! eg. db::get_array($sql, '#', array('id', 'value')) |
||||
{ |
||||
if (isset($values)) |
||||
{ |
||||
$result = array(); |
||||
if (is_string($values)) |
||||
foreach ($tmp as $row) |
||||
$result[] = $row[$values]; |
||||
else // $values is an array |
||||
{ |
||||
$values = array_flip($values); |
||||
foreach ($tmp as $row) |
||||
$result[] = array_intersect_key($row, $values); |
||||
} |
||||
} |
||||
else // $values is null |
||||
$result = $rs->fetch_all(MYSQLI_ASSOC); // MYSQLI_ASSOC | MYSQLI_NUM | MYSQLI_BOTH |
||||
} |
||||
else |
||||
{ |
||||
$result = array(); |
||||
$tmp = $rs->fetch_all(MYSQLI_ASSOC); // MYSQLI_ASSOC || MYSQLI_NUM || MYSQLI_BOTH WARNING: This has caused memory issues before!!! Unable to allocate n bytes!!! `SELECT * FROM city_catchment` = 2,981,304 rows | `data length` = 31MB (will be even larger in PHP) |
||||
|
||||
if (is_string($indexes)) |
||||
if (isset($values)) |
||||
if (is_string($values)) |
||||
foreach ($tmp as $row) |
||||
$result[$row[$indexes]] = $row[$values]; |
||||
else // $values is an array |
||||
{ |
||||
$values = array_flip($values); |
||||
foreach ($tmp as $row) |
||||
$result[$row[$indexes]] = array_intersect_key($row, $values); |
||||
} |
||||
else // $values is null |
||||
foreach ($tmp as $row) // Note: I tried using &$row here but it was slightly slower when just returning the recset without any processing on it, maybe it's faster if we do something with the recset?? Was 9.1s vs 9.5s! |
||||
$result[$row[$indexes]] = $row; |
||||
else // $indexes is an array |
||||
{ |
||||
if (isset($values)) |
||||
if (is_string($values)) |
||||
foreach ($tmp as $row) |
||||
{ |
||||
$entry = &$result; |
||||
foreach ($indexes as $index) |
||||
$entry = &$entry[$row[$index]]; |
||||
$entry = $row[$values]; |
||||
} |
||||
else // $values is an array |
||||
{ |
||||
$values = array_flip($values); |
||||
foreach ($tmp as $row) |
||||
{ |
||||
$entry = &$result; |
||||
foreach ($indexes as $index) |
||||
$entry = &$entry[$row[$index]]; |
||||
$entry = array_intersect_key($row, $values); |
||||
} |
||||
} |
||||
else // $values is null |
||||
foreach ($tmp as $row) |
||||
{ |
||||
$entry = &$result; |
||||
foreach ($indexes as $index) |
||||
$entry = &$entry[$row[$index]]; |
||||
$entry = $row; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
else if ($rs->field_count === 2) // NOTE: We can construct this type of array from a larger (superset) array with the make_select_array() static HELPER function! |
||||
{ |
||||
$result = array(); |
||||
$tmp = $rs->fetch_all(MYSQLI_NUM); |
||||
foreach ($tmp as $row) |
||||
$result[$row[0]] = $row[1]; |
||||
} |
||||
else |
||||
$result = $rs->fetch_all(MYSQLI_ASSOC); // MYSQLI_ASSOC | MYSQLI_NUM | MYSQLI_BOTH |
||||
|
||||
$rs->free_result(); |
||||
while ($this->more_results() && $this->next_result()); // Added on 2 May 2013; To be compatible with Stored Procedures (CALL ...) |
||||
return $result; |
||||
} |
||||
|
||||
function lookup($sql) |
||||
{ |
||||
$rs = $this->query($sql, MYSQLI_USE_RESULT); |
||||
if ($rs->field_count == 1) |
||||
{ |
||||
$row = $rs->fetch_row(); |
||||
$rs->free_result(); |
||||
return $row[0]; |
||||
} |
||||
$row = $rs->fetch_assoc(); |
||||
$rs->free_result(); |
||||
return $row; |
||||
} |
||||
|
||||
function call($sql) // the original `lookup` code ... the `more_results` section is the only one required by stored procedures! |
||||
{ |
||||
//$this->real_query('CALL ' . $sql); // not sure if I want to do this ??? will have to concat the string, but call('CALL ...') looks funny with 2x call's! |
||||
$rs = $this->query($sql, MYSQLI_USE_RESULT); |
||||
if ($rs->field_count == 1) |
||||
{ |
||||
$row = $rs->fetch_row(); |
||||
$rs->free_result(); |
||||
while ($this->more_results() && $this->next_result()); // Added on 23 Feb 2013; To be compatible with Stored Procedures (CALL ...) |
||||
return $row[0]; |
||||
} |
||||
$row = $rs->fetch_assoc(); |
||||
$rs->free_result(); |
||||
while ($this->more_results() && $this->next_result()); // Added on 23 Feb 2013; To be compatible with Stored Procedures (CALL ...) |
||||
return $row; |
||||
} |
||||
|
||||
|
||||
// MySQL only accepts 3-byte UTF-8! SANITIZE our "UTF-8" string for MySQL! |
||||
// Taken from: http://stackoverflow.com/questions/8491431/remove-4-byte-characters-from-a-utf-8-string |
||||
static function utf8($str) |
||||
{ |
||||
return preg_replace('/[\x{10000}-\x{10FFFF}]/u', "\xEF\xBF\xBD", (string) $str); |
||||
} |
||||
function escape($value, $prefix = false) // AKA sanitize string! My NEW version which includes UTF-8 cleaning as well! I DO NOT KNOW WHERE THIS FUNCTION `escape` was being used ... so I `replaced` it on 22 July 2015 with my enhanced version `new_escape` which was built out of trying to replace the NULLable parameters ... |
||||
{ // CODE FROM new_esacpe() written on 22 July 2015 @ 5pm (about) ... updated and re-written on 11 Dec 2015! |
||||
if (is_numeric($value)) return ($prefix?'= ':null) . $value; |
||||
if (is_string($value)) return $value ? ($prefix?'= "':'"') . $this->real_escape_string(self::utf8($value)) . '"' : ($prefix?'= ""':'""'); |
||||
if (is_null($value)) return $prefix ? 'IS NULL' : 'NULL'; |
||||
foreach ($value as $key => &$v) |
||||
$v = $this->escape($v); |
||||
return ($prefix?'IN (':'(') . implode(', ', $value) . ')'; |
||||
} |
||||
// This function is used in post.php files to remove 4-byte UTF-8 characters (MySQL only accepts upto 3-bytes), pack multiple space values, trim and get only $length characters! |
||||
static function varchar($str, $length = 65535, $empty = '', $compact = true) |
||||
{ |
||||
//return mb_substr(str_squash(preg_replace('/[\x{10000}-\x{10FFFF}]/u', "\xEF\xBF\xBD", $str)), 0, $length); |
||||
//return mb_substr(trim(mb_ereg_replace('\s+', ' ', preg_replace('/[\x{10000}-\x{10FFFF}]/u', "\xEF\xBF\xBD", $str))), 0, $length); |
||||
$str = trim(mb_substr(trim(mb_ereg_replace($compact ? '\s+' : ' +', ' ', self::utf8($str))), 0, $length)); // 2x trim() because after shortening it, we could have a space at the end of our shortened (substr) string |
||||
return empty($str) ? $empty : $str; |
||||
} |
||||
|
||||
|
||||
|
||||
// eg. db::lock_tables('users WRITE', 'worlds READ'); |
||||
// eg. db::lock_tables('users, worlds'); |
||||
function lock_tables(/* $tables, ... */) |
||||
{ |
||||
$this->real_query('LOCK TABLES ' . implode(',', func_get_args())); |
||||
} |
||||
function unlock_tables() |
||||
{ |
||||
$this->real_query('UNLOCK TABLES'); |
||||
} |
||||
|
||||
|
||||
// Written on 4 Dec 2015 @ 8:45am |
||||
// This is mainly just a wrapper around fetch_all() |
||||
// According to someone else's benchmark, it uses slightly less memory and about 35% faster than a while loop |
||||
// Use this when we just need to loop through the results and don't really care about the indexes! |
||||
// http://php.net/manual/en/mysqli-result.fetch-all.php |
||||
function fast_fetch($sql, $resulttype = MYSQLI_ASSOC) |
||||
{ |
||||
$recset = $this->query($sql, MYSQLI_USE_RESULT); // MYSQLI_USE_RESULT was about 12% faster than MYSQLI_STORE_RESULT |
||||
$result = $recset->fetch_all($resulttype); // MYSQLI_ASSOC || MYSQLI_NUM || MYSQLI_BOTH |
||||
$recset->free(); |
||||
return $result; |
||||
} |
||||
|
||||
// `get_object(s)` functions written on 17 Nov 2016 |
||||
// http://php.net/manual/en/mysqli-result.fetch-object.php |
||||
// STUPID function! Its order of creation has changed between PHP 5.6 and 7 |
||||
// The `params` don't seem to be passed to the constructor, so not sure what that's about |
||||
function get_object($sql, $class_name = 'stdClass', array $params = null) |
||||
{ |
||||
$rs = $this->query($sql, MYSQLI_USE_RESULT); |
||||
$obj = is_null($params) ? $rs->fetch_object($class_name) : $rs->fetch_object($class_name, $params); |
||||
$rs->free_result(); |
||||
return $obj; |
||||
} |
||||
|
||||
// http://php.net/manual/en/mysqli-result.fetch-object.php |
||||
function get_objects($sql, $class_name = 'stdClass', array $params = null, $index = null) |
||||
{ |
||||
$result = array(); |
||||
$recset = $this->query($sql, MYSQLI_USE_RESULT); |
||||
if (isset($index)) |
||||
{ |
||||
if (is_null($params)) |
||||
while ($obj = $recset->fetch_object($class_name)) |
||||
$result[$obj->$index] = $obj; |
||||
else |
||||
while ($obj = $recset->fetch_object($class_name, $params)) |
||||
$result[$obj->$index] = $obj; |
||||
} |
||||
else |
||||
{ |
||||
if (is_null($params)) |
||||
while ($obj = $recset->fetch_object($class_name)) |
||||
$result[] = $obj; |
||||
else |
||||
while ($obj = $recset->fetch_object($class_name, $params)) |
||||
$result[] = $obj; |
||||
} |
||||
$recset->free_result(); |
||||
return $result; |
||||
} |
||||
|
||||
function get_random_id($field, $table, $min, $max) |
||||
{ |
||||
return $this->call('CALL spGetRandomID("' . $field . '","' . $table . '",' . $min . ',' . $max . ')'); |
||||
} |
||||
|
||||
static function curdate() |
||||
{ |
||||
static $curdate = null; |
||||
if ( ! isset($curdate)) $curdate = date('Y-m-d'); // `fast` PHP server date version! |
||||
return $curdate; |
||||
/* |
||||
// taken from some older `request.php` init() code, but I don't think should be in there! This is a database lookup of the DATABASE server `curdate` which should be `more accurate` but slower! |
||||
$tmp = db::lookup('SELECT UNIX_TIMESTAMP() AS timestamp, CURDATE() AS curdate'); |
||||
self::$timestamp = $tmp['timestamp']; |
||||
self::$curdate = $tmp['curdate']; // database `slower` version |
||||
*/ |
||||
} |
||||
|
||||
} |
@ -0,0 +1,54 @@
@@ -0,0 +1,54 @@
|
||||
<?php |
||||
/** |
||||
* @see https://github.com/zendframework/zend-stratigility for the canonical source repository |
||||
* @copyright Copyright (c) 2015-2016 Zend Technologies USA Inc. (http://www.zend.com) |
||||
* @license https://github.com/zendframework/zend-stratigility/blob/master/LICENSE.md New BSD License |
||||
*/ |
||||
|
||||
namespace Twister; |
||||
|
||||
use Psr\Http\Message\ResponseInterface as Response; |
||||
use Psr\Http\Message\ServerRequestInterface as Request; |
||||
|
||||
/** |
||||
* Middleware. |
||||
* |
||||
* Middleware accepts a request and a response, and optionally a |
||||
* callback `$next` (called if the middleware wants to allow the *next* |
||||
* middleware to process the incoming request, or to delegate output to another |
||||
* process). |
||||
* |
||||
* Middleware that does not need or desire further processing should not |
||||
* call `$next`, and should instead return a response. |
||||
* |
||||
* For the purposes of Stratigility, `$next` is typically one of either an instance |
||||
* of `Next` or an instance of `NoopFinalHandler`, and, as such, should follow |
||||
* those calling semantics. |
||||
*/ |
||||
interface PipelineInterface |
||||
{ |
||||
/** |
||||
* Process an incoming request and/or response. |
||||
* |
||||
* Accepts a server-side request and a response instance, and does |
||||
* something with them. |
||||
* |
||||
* If the response is not complete and/or further processing would not |
||||
* interfere with the work done in the middleware, or if the middleware |
||||
* wants to delegate to another process, it can use the `$next` callable |
||||
* if present: |
||||
* |
||||
* <code> |
||||
* return $next($request, $response); |
||||
* </code> |
||||
* |
||||
* Middleware MUST return a response, or the result of $next (which should |
||||
* return a response). |
||||
* |
||||
* @param Request $request |
||||
* @param Response $response |
||||
* @param callable $next |
||||
* @return Response |
||||
*/ |
||||
public function __invoke(Request $request, Response $response, callable $next); |
||||
} |
@ -0,0 +1,8 @@
@@ -0,0 +1,8 @@
|
||||
<?php |
||||
|
||||
namespace Twister; |
||||
|
||||
class Router |
||||
{ |
||||
|
||||
} |
@ -0,0 +1,87 @@
@@ -0,0 +1,87 @@
|
||||
<?php |
||||
|
||||
namespace Twister; |
||||
|
||||
class Session |
||||
{ |
||||
private static $_db = null; |
||||
|
||||
static function start(db &$db) |
||||
{ |
||||
session_set_save_handler('session::open', 'session::close', 'session::read', 'session::write', 'session::destroy', 'session::gc'); |
||||
register_shutdown_function('session_write_close'); |
||||
session_set_cookie_params(0, '/', null, true, true); |
||||
self::$_db = $db; |
||||
session_start(); |
||||
} |
||||
|
||||
static function open($sp, $sn) |
||||
{ |
||||
return true; |
||||
} |
||||
|
||||
static function close() |
||||
{ |
||||
return true; |
||||
} |
||||
|
||||
static function read($id) |
||||
{ |
||||
if (isset($_COOKIE[session_name()])) |
||||
{ |
||||
if (!ctype_xdigit($id) || strlen($id) !== 32) die('Invalid Session ID: ' . $id); |
||||
if ($rs = self::$_db->query('SELECT SQL_NO_CACHE data FROM sessions WHERE id = 0x' . $id . ' LIMIT 1')) |
||||
{ |
||||
$row = $rs->fetch_row(); |
||||
$rs->free_result(); |
||||
return (string) $row[0]; |
||||
} |
||||
} |
||||
return ''; |
||||
} |
||||
|
||||
static function write($id, $data) |
||||
{ |
||||
if (isset($_COOKIE[session_name()])) // WARNING: This will NOT write the session on the first page view! The cookie MUST be created/set first, which requires another page view before it works! This prevents bots from creating sessions! But during testing a new session_id() or browser, it can be confusing because you won't see a new session until your second page view! |
||||
{ |
||||
$data = empty($data) ? 'NULL' : '"' . self::$_db->real_escape_string($data) . '"'; |
||||
self::$_db->real_query('INSERT INTO sessions (id, timestamp, persistent, data) VALUES (0x' . $id . ', UNIX_TIMESTAMP(), 0, ' . $data . ') ON DUPLICATE KEY UPDATE timestamp = UNIX_TIMESTAMP(), data = ' . $data); |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
// This is ONLY required in the login page! |
||||
static function create_persistent_session() |
||||
{ |
||||
self::$_db->real_query('INSERT INTO sessions (id, timestamp, persistent, data) VALUES (0x' . session_id() . ', UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), NULL)'); |
||||
} |
||||
|
||||
/* |
||||
static function login($user_id, $persistent) |
||||
{ |
||||
session_regenerate_id(true); |
||||
setcookie(session_name(), session_id(), $persistent ? 0x7fffffff : 0, '/'); |
||||
$_SESSION['id'] = $user_id; |
||||
self::$_db->real_query('INSERT INTO sessions (id, timestamp, persistent, user_id, data) VALUES (0x' . session_id() . ', UNIX_TIMESTAMP(), ' . ($persistent ? 'UNIX_TIMESTAMP(), ' : '0, ') . $user_id . ', "")'); |
||||
} |
||||
*/ |
||||
|
||||
// Taken from: http://www.php.net/manual/en/function.session-destroy.php |
||||
// `session_destroy() destroys all of the data associated with the current session. It does not unset any of the global variables associated with the session, or unset the session cookie.` |
||||
static function destroy($id) |
||||
{ |
||||
self::$_db->real_query('DELETE FROM sessions WHERE id = 0x' . $id); |
||||
return true; |
||||
} |
||||
|
||||
static function gc($ttl) |
||||
{ |
||||
self::$_db->real_query('DELETE FROM sessions WHERE timestamp < UNIX_TIMESTAMP() - ' . $ttl . ' AND persistent = 0'); // $ttl = 1440 (default) = 24 minutes |
||||
if (mt_rand(0, 99) == 0) |
||||
{ |
||||
self::$_db->real_query('OPTIMIZE TABLE sessions'); // optional routine maintenance |
||||
self::$_db->real_query('FLUSH QUERY CACHE'); // optional routine maintenance |
||||
} |
||||
return true; |
||||
} |
||||
} |
@ -0,0 +1,69 @@
@@ -0,0 +1,69 @@
|
||||
<?php // AKA player || visitor (bot/agent/human) || manager
|
||||
|
||||
namespace Twister; |
||||
|
||||
class User |
||||
{ |
||||
public $id = 0; |
||||
private $_properties = null; |
||||
private $_db = null; |
||||
|
||||
private $_permissions = null; |
||||
|
||||
function __construct(db &$db) |
||||
{ |
||||
$this->_db = $db; |
||||
if (isset($_SESSION['id'])) |
||||
{ |
||||
$this->id = $_SESSION['id']; |
||||
// $this->load_config(); |
||||
// $this->load_profile(); |
||||
$this->load_permissions(); |
||||
} |
||||
// else |
||||
// $this->id = false; |
||||
// TODO: This section needs work! |
||||
else if (/*request::$https && */ isset($_COOKIE['HTTPS_ONLY'])) // NOTE: We should ALREADY be on request::$https!!! Because it's checked before user::init() is called! |
||||
{ // We need to redirect to the user login page ... and stop there! ... actually ... we are just gonna clear the HTTPS_ONLY cookie, because the user.id is no longer valid! |
||||
// redirect('/login?next=' . urlencode(env::canonical('https:')) . '&message=session-expired'); // `URI request too long` ... basically it goes into an infinite loop! |
||||
setcookie('HTTPS_ONLY', null, -1, '/'); |
||||
unset($_COOKIE['HTTPS_ONLY']); |
||||
// env::https_redirect('/login?next=' . url_encode(...)); // User session probably expired! maybe we should show a message in /login and unset the cookie there! Like `Your session has expired please login again!` or whatever! |
||||
} |
||||
} |
||||
|
||||
private static function load_permissions() |
||||
{ |
||||
self::$_permissions = $this->_db->get_array( 'SELECT SQL_CACHE ' . // cached because these tables are less frequenty updated! |
||||
'g.alias as g_alias,' . |
||||
'p.alias as p_alias,' . |
||||
'acl.object_id' . |
||||
' FROM acl' . |
||||
' JOIN acl_permissions p ON p.id = acl.permission_id' . |
||||
' JOIN acl_groups g ON g.id = p.group_id' . |
||||
' WHERE acl.user_id = ' . $this->id . |
||||
' AND acl.disabled = 0', |
||||
array('g_alias', 'p_alias', 'object_id'), array('object_id')); |
||||
} |
||||
static function permission($group_alias, $permission_alias, $query_data = null, $object = 0) |
||||
{ |
||||
if (!is_array($object)) |
||||
{ |
||||
if (isset(self::$_permissions[$group_alias][$permission_alias][$object])) return true; |
||||
} |
||||
else // used when we want to specify default zero OR a value ... eg. array(0, 13); |
||||
foreach ($object as $obj) if (isset(self::$_permissions[$group_alias][$permission_alias][$obj])) return true; |
||||
if (isset(self::$_permissions['administrators']['super'])) return true; // super-admin bypass! |
||||
if (isset($query_data)) |
||||
{ |
||||
if (is_string($query_data)) $query_data = array('next' => $query_data); |
||||
$query_data['warning'] = 'Protected Area! Login with relevant permissions required!'; // <== TODO: Translate this!!! Or send a constant! |
||||
redirect('/login', $query_data); |
||||
} |
||||
return false; |
||||
} |
||||
static function permissions($group_alias, $permission_alias) |
||||
{ |
||||
return isset(self::$_permissions[$group_alias][$permission_alias]) ? array_keys(self::$_permissions[$group_alias][$permission_alias]) : array(); |
||||
} |
||||
} |
Loading…
Reference in new issue