diff --git a/.idea/Session.iml b/.idea/Session.iml deleted file mode 100644 index c956989..0000000 --- a/.idea/Session.iml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index 8b47dd8..0000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/php.xml b/.idea/php.xml deleted file mode 100644 index 4f0611e..0000000 --- a/.idea/php.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 94a25f7..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/README.md b/README.md index 9e6c643..179550f 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ Basic usage: ## Registering Error Handler (optional) ```php #This method must be implemented before Session::start -Session::registerErrorHandler(function($error, $error_code) +Session::registerErrorHandler(function(string $error, int $error_code) { #Debug::Log($error); }); @@ -118,3 +118,37 @@ $session = Session::start($optional_session_namespace, $auto_save); ``` Which allows uncommitted (forgot to commit) changes to saves automatically. Is set to `false`, uncommitted changes will be discarded. + +## Change Log *v1.03.0* + - `start` method now accepts `null` arg as namespace. + - Default session driver is now set to `file`. + - Auto delete session after rotate is now defaulted to `false`. + - A `setConfigPath` method has been added. + ```php + #This method must be implemented before Session::start + Session::setConfigPath('my/config/path/config.php'); + ``` + - A set queue has been added + ```php + $session->name = 'foo'; + $session->name = 'foo1'; + +var_dump($session->name); # Outputs 'foo1' + + $session->name('foo') + $session->name('foo1'); + + var_dump($session->name); # Outputs ['foo', 'foo1']; + + # Array + $session->name = ['foo', 'bar', ...]; + # is same as + $session->name('foo', 'bar', ...); + ``` + +When flash are placed using the new queue method, they will be dispatched one after another on each request +```php +$session->flash->message('invalid 1', 'invalid 2'); +``` +With the above `invalid 1` will disptach on first load/reload and `invalid 2` on second. + diff --git a/src/Session.php b/src/Session.php index 41f32a8..b4745a6 100644 --- a/src/Session.php +++ b/src/Session.php @@ -44,22 +44,18 @@ class Session { private static $initialized = []; - private static $started = false; - private static $class = null; - private static $ssl_enabled = true; - public static $write = false; - + private static $custom_config = null; public static $id = ''; private static function init() { $DS = DIRECTORY_SEPARATOR; - $path = __DIR__ . $DS . 'Session' . $DS; - $config = include($path . 'config.php'); + $path = __DIR__ . "{$DS}Session{$DS}"; + $config = include(self::$custom_config ?? $path . 'config.php'); self::$initialized = $config; $driver = $config['driver']; @@ -67,13 +63,13 @@ private static function init() if(! is_dir($path . self::$class)) { - throw new \RuntimeException('No driver found for ' . self::$class); + throw new RuntimeException('No driver found for ' . self::$class); } self::$ssl_enabled = self::$initialized['encrypt_data']; if (self::$ssl_enabled && ! extension_loaded('openssl')) { - throw new \RuntimeException('The openssl extension is missing. Please check your PHP configuration.'); + throw new RuntimeException('The openssl extension is missing. Please check your PHP configuration.'); } @@ -81,7 +77,7 @@ private static function init() { if (! extension_loaded(self::$class)) { - throw new \RuntimeException('The ' . self::$class . ' extension is missing. Please check your PHP configuration.'); + throw new RuntimeException('The ' . self::$class . ' extension is missing. Please check your PHP configuration.'); } } @@ -89,7 +85,7 @@ private static function init() $secured = $config['secure']; if ($secured !== true && $secured !== false && $secured !== null) { - throw new \RuntimeException('config.secure expected value to be a boolean or null'); + throw new RuntimeException('config.secure expected value to be a boolean or null'); } if ($secured == null) @@ -145,11 +141,11 @@ public static function id(string $id = ''): string { if (self::$started) { - throw new \RuntimeException('Session is active. The session id must be set before Session::start().'); + throw new RuntimeException('Session is active. The session id must be set before Session::start().'); } elseif (headers_sent($filename, $line_num)) { - throw new \RuntimeException(sprintf('ID must be set before any output is sent to the browser (file: %s, line: %s)', $filename, $line_num)); + throw new RuntimeException(sprintf('ID must be set before any output is sent to the browser (file: %s, line: %s)', $filename, $line_num)); } elseif (preg_match('/^[-,a-zA-Z0-9]{1,128}$/', $id) < 1) { @@ -165,6 +161,19 @@ public static function id(string $id = ''): string } + /** + * Sets a file where config settings will be loaded from. + * + * @param string $path_to_file + */ + public static function setConfigFile(string $path_to_file) + { + if (! is_file($path_to_file)) + { + throw new RuntimeException('config was not found in (' . $path_to_file . ') or not enough permission.'); + } + self::$custom_config = $path_to_file; + } /** * starts a new session * @@ -172,7 +181,7 @@ public static function id(string $id = ''): string * @param bool $auto_commit (Alternative for https://github.com/Ghostff/Session/issues/4) * @return Save */ - public static function start(string $namespace = '__GLOBAL', bool $auto_commit = true): Save + public static function start(string $namespace = null, bool $auto_commit = true): Save { if (empty(self::$initialized)) { @@ -180,7 +189,7 @@ public static function start(string $namespace = '__GLOBAL', bool $auto_commit = } self::$started = true; - self::$initialized['namespace'] = $namespace; + self::$initialized['namespace'] = $namespace ?? '__GLOBAL'; $handler = new Save(self::$initialized); if ($auto_commit) { @@ -296,6 +305,7 @@ public static function encrypt(string $data): string $iv = substr($salted, 32,16); $encrypted_data = openssl_encrypt($data, 'AES-256-CBC', $key, 1, $iv); + return base64_encode($salt . $encrypted_data); } -} \ No newline at end of file +} diff --git a/src/Session/Cookie/Handler.php b/src/Session/Cookie/Handler.php index a918c3a..264f331 100644 --- a/src/Session/Cookie/Handler.php +++ b/src/Session/Cookie/Handler.php @@ -40,12 +40,11 @@ declare(strict_types=1); namespace Session\Cookie; -use Session; +use Session, SessionHandlerInterface; -class Handler implements \SessionHandlerInterface +class Handler implements SessionHandlerInterface { - public function open($savePath, $sessionName) { return true; @@ -87,4 +86,4 @@ public function gc($max_life_time): bool { return true; } -} \ No newline at end of file +} diff --git a/src/Session/File/Handler.php b/src/Session/File/Handler.php index d6dfe5a..4cb0307 100644 --- a/src/Session/File/Handler.php +++ b/src/Session/File/Handler.php @@ -39,12 +39,11 @@ declare(strict_types=1); - namespace Session\File; -use Session; +use Session, SessionHandlerInterface; -class Handler implements \SessionHandlerInterface +class Handler implements SessionHandlerInterface { private $savePath; @@ -100,4 +99,4 @@ public function gc($max_life_time): bool return true; } -} \ No newline at end of file +} diff --git a/src/Session/Memcached/Handler.php b/src/Session/Memcached/Handler.php index ca90bd4..cabcc42 100644 --- a/src/Session/Memcached/Handler.php +++ b/src/Session/Memcached/Handler.php @@ -37,28 +37,23 @@ * */ - declare(strict_types=1); namespace Session\Memcached; -use Session, Memcached; +use Memcached, RuntimeException, Session, SessionHandlerInterface; -class Handler implements \SessionHandlerInterface +class Handler implements SessionHandlerInterface { - private $config = []; - private $conn = null; - private $expire = 0; - private $name = null; public function __construct(array $config) { if (! isset($config['memcached'])) { - throw new \RuntimeException('No memcached configuration found in config file.'); + throw new RuntimeException('No memcached configuration found in config file.'); } $this->expire = $config['expiration']; @@ -112,4 +107,4 @@ public function gc($max_life_time): bool { return true; } -} \ No newline at end of file +} diff --git a/src/Session/Pdo/Handler.php b/src/Session/Pdo/Handler.php index 3635d01..2995067 100644 --- a/src/Session/Pdo/Handler.php +++ b/src/Session/Pdo/Handler.php @@ -37,28 +37,23 @@ * */ - declare(strict_types=1); namespace Session\Pdo; -use PDO, Session; +use PDO, PDOException, RuntimeException, Session, SessionHandlerInterface; -class Handler implements \SessionHandlerInterface +class Handler implements SessionHandlerInterface { - private $conn = null; - private $table = null; - private $persistent = false; - public function __construct(array $config) { if (! isset($config['pdo'])) { - throw new \RuntimeException('No pdo configuration found in config file.'); + throw new RuntimeException('No pdo configuration found in config file.'); } $config = $config['pdo']; @@ -77,7 +72,7 @@ public function __construct(array $config) { $this->conn->query('SELECT 1 FROM `' . $table . '` LIMIT 1'); } - catch (\PDOException $e) + catch (PDOException $e) { $this->conn->query('CREATE TABLE `' . $table . '` ( `id` varchar(250) NOT NULL, @@ -151,4 +146,4 @@ public function gc($max_life_time): bool $statement = null; return $completed; } -} \ No newline at end of file +} diff --git a/src/Session/Redis/Handler.php b/src/Session/Redis/Handler.php index 0f32b6e..8e4be36 100644 --- a/src/Session/Redis/Handler.php +++ b/src/Session/Redis/Handler.php @@ -37,28 +37,23 @@ * */ - declare(strict_types=1); namespace Session\Redis; -use Session, Redis; +use Redis, RuntimeException, Session, SessionHandlerInterface; -class Handler implements \SessionHandlerInterface +class Handler implements SessionHandlerInterface { - private $config = []; - private $conn = null; - private $expire = 0; - private $name = null; public function __construct(array $config) { if (! isset($config['redis'])) { - throw new \RuntimeException('No Redis configuration found in config file.'); + throw new RuntimeException('No Redis configuration found in config file.'); } $this->expire = $config['expiration']; @@ -114,4 +109,4 @@ public function gc($max_life_time): bool { return true; } -} \ No newline at end of file +} diff --git a/src/Session/Save.php b/src/Session/Save.php index b78f135..37b42fd 100644 --- a/src/Session/Save.php +++ b/src/Session/Save.php @@ -37,30 +37,66 @@ * */ - declare(strict_types=1); namespace Session; + +use RuntimeException; use Session; +/** + * @property Save $flash + * @property Save $remove + */ class Save { + /** + * @var array + */ private $config = []; + /** + * @var bool + */ + private $internal = false; + + /** + * @var array + */ + private static $checkpoint = []; + + /** + * @var bool + */ public $all_was_committed = true; + /** + * Gets client IP address. + * + * @return string + */ private function ip(): string { return $_SERVER['HTTP_CLIENT_IP'] ?? ($_SERVER['HTTP_X_FORWARDE‌​D_FOR'] ?? $_SERVER['REMOTE_ADDR']); } - private function browser() + /** + * Gets clients browser. + * + * @return string + */ + private function browser(): string { #you can make this more sophisticated lol - return $_SERVER['HTTP_USER_AGENT']; + return $_SERVER['HTTP_USER_AGENT'] ?? ''; } + /** + * Session validation. + * + * @return bool + */ private function checkSession(): bool { @@ -190,7 +226,7 @@ public function __set(string $name, $value) if ($last == 'remove') { - throw new \RuntimeException('you cant use remove to set a value'); + throw new RuntimeException('you cant use remove to set a value'); } else { @@ -199,6 +235,54 @@ public function __set(string $name, $value) } } + /** + * Append data to an existing session. (cast existing to array) + * + * @param string $name + * @param array $values + * @return Save + */ + public function __call(string $name, array $values): Save + { + $this->internal = true; + $called = $this->config['last']; + + $stack = ($called == 'flash') ? ($this->{$called}->{$name} ?? []) : $this->{$name}; + + $key = "{$called}:{$name}"; + if (! array_key_exists($key, self::$checkpoint)) + { + self::$checkpoint[$key] = 0; + } + + foreach ($values as $value) + { + if ($called == 'flash') + { + $stack[':bittr_queued'][self::$checkpoint[$key]] = $value; + } + else + { + $stack[self::$checkpoint[$key]] = $value; + } + + self::$checkpoint[$key]++; + } + + if ($called == 'flash') + { + $this->{$called}->{$name} = $stack; + } + else + { + $this->{$name} = $stack; + } + + $this->internal = false; + + return $this; + } + /** * Retrieves value from active session or segment * @@ -233,8 +317,23 @@ public function __get(string $name) elseif ($last == 'flash') { $value = $this->config['session'][$namespace][$segment][$last][$name]; - unset($this->config['session'][$namespace][$segment][$last][$name]); - $this->commit(); + if (! $this->internal) + { + $flash =& $this->config['session'][$namespace][$segment][$last][$name]; + if (isset($flash[':bittr_queued'])) + { + $value = array_shift($flash[':bittr_queued']); + if (empty($flash[':bittr_queued'])) + { + $flash = null; + } + } + else + { + unset($flash); + } + $this->commit(); + } return $value; } if ($last == 'remove') @@ -270,7 +369,7 @@ public function all(string $segment = ''): array } else { - throw new \RuntimeException('segment ' . $segment . ' does not exist.'); + throw new RuntimeException('segment ' . $segment . ' does not exist.'); } } } @@ -304,11 +403,11 @@ public function commit() * @param bool $delete_old * @param bool $write_nd_close */ - public function rotate(bool $delete_old = true, bool $write_nd_close = true) + public function rotate(bool $delete_old = false, bool $write_nd_close = true) { if (headers_sent($filename, $line_num)) { - throw new \RuntimeException(sprintf('ID must be regenerated before any output is sent to the browser. (file: %s, line: %s)', $filename, $line_num)); + throw new RuntimeException(sprintf('ID must be regenerated before any output is sent to the browser. (file: %s, line: %s)', $filename, $line_num)); } session_start(); @@ -323,6 +422,8 @@ public function rotate(bool $delete_old = true, bool $write_nd_close = true) } /** + * Clears all data in a specific segment or current namespace. + * * Clears all the data is a specific namespace or segment * @param string $segment */ @@ -353,7 +454,10 @@ public function clear(string $segment = '') } /** + * Checks if data exist is current namespace or segment. + * * @param string $name + * @param string|null $segment * @param bool $in_flash * @return bool */ @@ -388,4 +492,4 @@ public function destroy() $this->config['session'] = []; } -} \ No newline at end of file +} diff --git a/src/Session/Segment.php b/src/Session/Segment.php index 2fa2e58..e4dae58 100644 --- a/src/Session/Segment.php +++ b/src/Session/Segment.php @@ -37,20 +37,33 @@ * */ - declare(strict_types=1); - namespace Session; class Segment { + /** + * @var null|string + */ private $segment = null; + /** + * @var array|null + */ private $config = null; + /** + * @var null + */ private $handler = null; + /** + * Segment constructor. + * @param string $name + * @param array $config + * @param $handler + */ public function __construct(string $name, array &$config, $handler) { $this->segment = 'segment:' . $name; @@ -58,15 +71,24 @@ public function __construct(string $name, array &$config, $handler) $this->handler = $handler; } + /** + * @param string $name + * @param $value + * @return mixed + */ public function __set(string $name, $value) { $this->config['segment'] = $this->segment; return $this->handler->{$name} = $value; } + /** + * @param string $name + * @return mixed + */ public function __get(string $name) { $this->config['segment'] = $this->segment; return $this->handler->{$name}; } -} \ No newline at end of file +} diff --git a/src/Session/config.php b/src/Session/config.php index dbcb379..7ccc3cd 100644 --- a/src/Session/config.php +++ b/src/Session/config.php @@ -38,7 +38,7 @@ */ return [ - 'driver' => 'cookie', # Name of session driver to use: [file|pdo|cookie|redis|memcached] + 'driver' => 'file', # Name of session driver to use: [file|pdo|cookie|redis|memcached] 'name' => '_Bittr_SESSID', # session name 'cache_limiter' => 'none', # http://php.net/manual/en/function.session-cache-limiter.php