1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222
|
<?php
/**
* Matomo - free/libre analytics platform
*
* @link https://matomo.org
* @license https://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
namespace Piwik\Session\SaveHandler;
use Piwik\Db;
use Piwik\DbHelper;
use Exception;
use Piwik\SettingsPiwik;
use Piwik\Updater\Migration;
use Zend_Session;
/**
* Database-backed session save handler
*
*/
class DbTable implements \SessionHandlerInterface
{
public static $wasSessionToLargeToRead = false;
protected $config;
protected $maxLifetime;
public const TABLE_NAME = 'session';
public const TOKEN_HASH_ALGO = 'sha512';
/**
* @param array $config
*/
public function __construct($config)
{
$this->config = $config;
$this->maxLifetime = ini_get('session.gc_maxlifetime');
}
private function hashSessionId($id)
{
$salt = SettingsPiwik::getSalt();
return hash(self::TOKEN_HASH_ALGO, $id . $salt);
}
/**
* Destructor
*
* @return void
*/
public function __destruct()
{
Zend_Session::writeClose();
}
/**
* Open Session - retrieve resources
*
* @param string $save_path
* @param string $name
*/
public function open($save_path, $name): bool
{
Db::get()->getConnection();
return true;
}
/**
* Close Session - free resources
*
*/
public function close(): bool
{
return true;
}
/**
* Read session data
*
* @param string $id
* @return string
*/
#[\ReturnTypeWillChange]
public function read($id)
{
$id = $this->hashSessionId($id);
$sql = 'SELECT ' . $this->config['dataColumn'] . ' FROM `' . $this->config['name'] . '`'
. ' WHERE ' . $this->config['primary'] . ' = ?'
. ' AND ' . $this->config['modifiedColumn'] . ' + ' . $this->config['lifetimeColumn'] . ' >= ?';
$result = $this->fetchOne($sql, [$id, time()]);
if (!$result) {
$result = '';
}
return $result;
}
private function fetchOne($sql, $bind)
{
try {
$result = Db::get()->fetchOne($sql, $bind);
} catch (Exception $e) {
if (Db::get()->isErrNo($e, Migration\Db::ERROR_CODE_TABLE_NOT_EXISTS)) {
$this->migrateToDbSessionTable();
$result = Db::get()->fetchOne($sql, $bind);
} else {
throw $e;
}
}
return $result;
}
private function query($sql, $bind)
{
try {
$result = Db::get()->query($sql, $bind);
} catch (Exception $e) {
if (Db::get()->isErrNo($e, Migration\Db::ERROR_CODE_TABLE_NOT_EXISTS)) {
$this->migrateToDbSessionTable();
$result = Db::get()->query($sql, $bind);
} else {
throw $e;
}
}
return $result;
}
/**
* Write Session - commit data to resource
*
* @param string $id
* @param mixed $data
*/
public function write($id, $data): bool
{
$id = $this->hashSessionId($id);
$sql = 'INSERT INTO ' . $this->config['name']
. ' (' . $this->config['primary'] . ','
. $this->config['modifiedColumn'] . ','
. $this->config['lifetimeColumn'] . ','
. $this->config['dataColumn'] . ')'
. ' VALUES (?,?,?,?)'
. ' ON DUPLICATE KEY UPDATE '
. $this->config['modifiedColumn'] . ' = ?,'
. $this->config['lifetimeColumn'] . ' = ?,'
. $this->config['dataColumn'] . ' = ?';
$this->query($sql, [$id, time(), $this->maxLifetime, $data, time(), $this->maxLifetime, $data]);
return true;
}
/**
* Destroy Session - remove data from resource for
* given session id
*
* @param string $id
*/
public function destroy($id): bool
{
$id = $this->hashSessionId($id);
$sql = 'DELETE FROM `' . $this->config['name'] . '` WHERE ' . $this->config['primary'] . ' = ?';
$this->query($sql, [$id]);
return true;
}
/**
* Destroys all Sessions - removes all rows in Session table
*/
public function destroyAll(): bool
{
$sql = 'TRUNCATE TABLE `' . $this->config['name'] . '`';
$this->query($sql, []);
return true;
}
/**
* Garbage Collection - remove old session data older
* than $maxlifetime (in seconds)
*
* @param int $maxlifetime timestamp in seconds
* @return bool always true
*/
#[\ReturnTypeWillChange]
public function gc($maxlifetime)
{
$sql = 'DELETE FROM `' . $this->config['name'] . '`'
. ' WHERE ' . $this->config['modifiedColumn'] . ' + ' . $this->config['lifetimeColumn'] . ' < ?';
$this->query($sql, [time()]);
return true;
}
private function migrateToDbSessionTable()
{
// happens when updating from Piwik 1.4 or earlier to Matomo 3.7+
// in this case on update it will change the session handler to dbtable, but it hasn't performed
// the DB updates just yet which means the session table won't be available as it was only added in
// Piwik 1.5 => results in a sql error the session table does not exist
try {
$sql = DbHelper::getTableCreateSql(self::TABLE_NAME);
Db::query($sql);
} catch (Exception $e) {
if (!Db::get()->isErrNo($e, Migration\Db::ERROR_CODE_TABLE_EXISTS)) {
throw $e;
}
}
}
}
|