'Session Handler Database', 'version' => 2, 'summary' => "Installing this module makes ProcessWire store sessions in the database rather than the file system. Note that this module will log you out after install or uninstall.", 'installs' => array('ProcessSessionDB') ); } /** * Table created by this module * */ const dbTableName = 'sessions'; /** * Quick reference to database * */ protected $database; /** * Construct * */ public function __construct() { parent::__construct(); $this->database = wire('database'); $this->set('useIP', 0); // track IP address? $this->set('useUA', 0); // track query string? } /** * Read and return data for session indicated by $id * * @param string $id Session ID * @return string Serialized data or blank string if none * */ public function read($id) { $table = self::dbTableName; $database = $this->database; $data = ''; $query = $database->prepare("SELECT GET_LOCK(:id, 10)"); $query->execute(array(":id" => $id)); $query = $database->prepare("SELECT data FROM `$table` WHERE id=:id"); $query->execute(array(":id" => $id)); if($query->rowCount()) { $data = $query->fetchColumn(); if(empty($data)) $data = ''; } $query->closeCursor(); return $data; } /** * Write the given $data for the given session ID * * @param string $id Session ID * @param string Serialized data to write * @return bool * */ public function write($id, $data) { $table = self::dbTableName; $database = $this->database; $user = $this->wire('user'); $page = $this->wire('page'); $id = $database->escapeStr($id); $user_id = $user && $user->id ? (int) $user->id : 0; $pages_id = $page && $page->id ? (int) $page->id : 0; $data = $database->escapeStr($data); $ua = ($this->useUA && isset($_SERVER['HTTP_USER_AGENT'])) ? $database->escapeStr(substr($_SERVER['HTTP_USER_AGENT'], 0, 255)) : ''; $ip = $this->useIP ? ((int) wire('session')->getIP(true)) : ''; $s = "user_id=$user_id, pages_id=$pages_id, data='$data'"; if($ip) $s .= ", ip=$ip"; if($ua) $s .= ", ua='$ua'"; $sql = "INSERT INTO $table SET id='$id', $s "; $sql .= "ON DUPLICATE KEY UPDATE $s, ts=NOW()"; $query = $database->prepare($sql); $result = $query->execute() ? true : false; $database->exec("SELECT RELEASE_LOCK('$id')"); return $result; } /** * Destroy the session indicated by the given session ID * * @param string $id Session ID * @return bool True on success, false on failure * */ public function destroy($id) { $table = self::dbTableName; $database = $this->database; $query = $database->prepare("DELETE FROM `$table` WHERE id=:id"); $query->execute(array(":id" => $id)); setcookie(session_name(), '', time()-3600); return true; } /** * Garbage collection: remove stale sessions * * @param int $seconds Max lifetime of a session * @return bool True on success, false on failure * */ public function gc($seconds) { $table = self::dbTableName; $seconds = (int) $seconds; $sql = "DELETE FROM `$table` WHERE ts < DATE_SUB(NOW(), INTERVAL $seconds SECOND)"; return $this->database->exec($sql); } /** * Install sessions table * */ public function ___install() { $table = self::dbTableName; $sql = "CREATE TABLE `$table` (" . "id CHAR(32) NOT NULL, " . "user_id INT UNSIGNED NOT NULL, " . "pages_id INT UNSIGNED NOT NULL, " . "data TEXT NOT NULL, " . "ts TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, " . "ip INT UNSIGNED NOT NULL DEFAULT 0, " . "ua VARCHAR(255) NOT NULL DEFAULT '', " . "PRIMARY KEY (id), " . "INDEX (pages_id), " . "INDEX (user_id), " . "INDEX (ts) " . ") ENGINE=InnoDB DEFAULT CHARSET=utf8"; $this->database->query($sql); } /** * Drop sessions table * */ public function ___uninstall() { $this->database->query("DROP TABLE " . self::dbTableName); } /** * Session configuration options * */ public static function getModuleConfigInputfields(array $data) { $form = new InputfieldWrapper(); // check if their DB table is the latest version $query = wire('database')->query("SHOW COLUMNS FROM " . self::dbTableName . " WHERE field='ip'"); if(!$query->rowCount()) { wire('modules')->error("DB format changed - You must uninstall this module and re-install before configuring."); return $form; } $description = __('Checking this box will enable the data to be displayed in your admin sessions list.', __FILE__); $f = wire('modules')->get('InputfieldCheckbox'); $f->attr('name', 'useIP'); $f->attr('value', 1); $f->attr('checked', empty($data['useIP']) ? '' : 'checked'); $f->label = __('Track IP addresses in session data?', __FILE__); $f->description = $description; $form->add($f); $f = wire('modules')->get('InputfieldCheckbox'); $f->attr('name', 'useUA'); $f->attr('value', 1); $f->attr('checked', empty($data['useUA']) ? '' : 'checked'); $f->label = __('Track user agent in session data?', __FILE__); $f->notes = __('The user agent typically contains information about the browser being used.', __FILE__); $f->description = $description; $form->add($f); return $form; } /** * Provides direct reference access to set values in the $data array * * For some reason PHP 5.4+ requires this, as it apparently doesn't see WireData * */ public function __set($key, $value) { $this->set($key, $value); } /** * Provides direct reference access to variables in the $data array * * For some reason PHP 5.4+ requires this, as it apparently doesn't see WireData * * Otherwise the same as get() * * @param string $key * */ public function __get($key) { return $this->get($key); } }