<?php
|
/**
|
* Klasa przechowująca dane przekazane przez użytkownika,
|
* w szczególności jego ustawienia.
|
*/
|
class BotSession {
|
/**
|
* Instancja PDO tworzona w metodzie {@link BotSession::init()}.
|
* @var PDO $PDO
|
*/
|
protected $PDO;
|
|
/**
|
* Katalog, w którym trzymane są dane sesyjne użytkowników.
|
* @var string $sessionDir
|
*/
|
protected $sessionDir;
|
|
/**
|
* Katalog, w którym trzymane są dane sesyjne użytkowników
|
* z poprzedniej wersji bota.
|
* @var string $legacySessionDir
|
*/
|
protected $legacySessionDir;
|
|
/**
|
* Nazwa modułu (max. 40 znaków), którego zmienne klasa aktualnie przetwarza,
|
* ustawiana metodą {@link BotSession::setClass()}.
|
* @var string $class
|
*/
|
protected $class = '';
|
|
/**
|
* Pseudo-URL użytkownika.
|
* @see BotUser
|
* @var string $user
|
*/
|
protected $user;
|
|
/**
|
* Inicjuje klasę dla podanego użytkownika
|
* @param string $user Pseudo-URL użytkownika
|
* @param string $sessionDir Katalog z danymi, domyślnie BOT_TOPDIR/database
|
* @param string $legacySessionDir Katalog z danymi ze starej wersji bota, domyślnie BOT_TOPDIR/db
|
*/
|
public function __construct($user, $sessionDir = NULL, $legacySessionDir = NULL) {
|
if(empty($sessionDir)) {
|
$sessionDir = BOT_TOPDIR.'/database';
|
}
|
if(empty($legacySessionDir)) {
|
$legacySessionDir = BOT_TOPDIR.'/db';
|
}
|
|
$this->user = $user;
|
$this->sessionDir = $sessionDir;
|
$this->legacySessionDir = $legacySessionDir;
|
}
|
|
/**
|
* Sprawdza ustawienie pola {@link BotSession::$class} oraz, jeśli nie została wykonana wcześniej,
|
* dokonuje inicjalizacji klasy.
|
* Metoda ta winna być wywoływana przez każdą publiczną funkcję operującą na danych.
|
* @throws Exception Wyjątek rzucany, gdy przed użyciem metody, nazwa klasy
|
* nie została ustawiona metodą {@link BotSession::setClass()}
|
*/
|
protected function init() {
|
if(empty($this->class)) {
|
throw new Exception('Przed użyciem mechanizmu sesji należy ustawić nazwę modułu za pomocą metody setClass - patrz "Poradnik tworzenia modułów", dział "Klasa BotMessage", rozdział "Pole $session".');
|
}
|
|
if($this->PDO) {
|
// Inicjalizacja została już przeprowadzona - wyjdź.
|
return;
|
}
|
|
$dbFile = $this->sessionDir.'/'.sha1(sha1($this->user)).'.sqlite';
|
|
$this->PDO = new PDO('sqlite:'.$dbFile);
|
$this->PDO->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
$this->PDO->setAttribute(PDO::ATTR_ORACLE_NULLS, PDO::NULL_TO_STRING);
|
|
$st = $this->PDO->query('SELECT COUNT(name) FROM sqlite_master WHERE type=\'table\' AND name=\'data\'');
|
$num = $st->fetch(PDO::FETCH_NUM);
|
$schemaExists = $num[0] > 0;
|
|
if($schemaExists) {
|
$this->updateDatabase();
|
} else {
|
try {
|
$this->createSchema();
|
$this->importLegacyData();
|
}
|
catch(Exception $e) {
|
// Import danych nie udał się - usuń pozostałości.
|
if(file_exists($dbFile)) {
|
@unlink($dbFile);
|
}
|
throw $e;
|
}
|
}
|
}
|
|
/**
|
* Ustawia nazwę modułu/klasy, której zmienne będą przetwarzane
|
* @param string $class Nazwa modułu
|
*/
|
public function setClass($class) {
|
$this->class = $class;
|
}
|
|
/**
|
* Pobiera zmienną o podanej nazwie (getter).
|
* @param string $name Nazwa zmiennej.
|
* @return mixed Wartość zmiennej lub NULL, jeśli zmienna nie istnieje.
|
*/
|
public function __get($name) {
|
$this->init();
|
|
$st = $this->PDO->prepare('SELECT value FROM data WHERE class=? AND name=?');
|
$st->execute(array($this->class, $name));
|
$st = $st->fetch(PDO::FETCH_ASSOC);
|
|
if(is_array($st)) {
|
return unserialize($st['value']);
|
}
|
|
return NULL;
|
}
|
|
/**
|
* Ustawia zmienną o podanej nazwie.
|
* @param string $name Nazwa zmiennej.
|
* @param mixed $value Wartość do ustawienia.
|
*/
|
public function __set($name, $value) {
|
$this->init();
|
|
$st = $this->PDO->prepare('INSERT OR REPLACE INTO data (class, name, value) VALUES (?, ?, ?)');
|
$st->execute(array($this->class, $name, serialize($value)));
|
}
|
|
/**
|
* Sprawdza czy podana zmienna została ustawiona.
|
* @param string $name Nazwa zmiennej do sprawdzenia.
|
* @return bool Czy zmienna istnieje?
|
*/
|
public function __isset($name) {
|
$this->init();
|
|
$st = $this->PDO->prepare('SELECT COUNT(name) FROM data WHERE class=? AND name=?');
|
$st->execute(array($this->class, $name));
|
$st = $st->fetch(PDO::FETCH_NUM);
|
|
return ($st[0] > 0);
|
}
|
|
/**
|
* Usuwa zmienną o podanej nazwie.
|
* @param string $name Nazwa zmiennej do usunięcia.
|
*/
|
public function __unset($name) {
|
$this->init();
|
|
$st = $this->PDO->prepare('DELETE FROM data WHERE class=? AND name=?');
|
$st->execute(array($this->class, $name));
|
}
|
|
/**
|
* Dodaje tablicę zmiennych do danych użytkownika.
|
* @param array $array Tablica zmiennych do dodania.
|
*/
|
public function push($array) {
|
$this->init();
|
|
$this->PDO->beginTransaction();
|
foreach($array as $name => $value) {
|
$this->__set($name, $value);
|
}
|
$this->PDO->commit();
|
}
|
|
/**
|
* Zwraca wszystkie ustawione zmienne dla modułu.
|
* @return array Lista wszystkich zmiennych.
|
*/
|
public function pull() {
|
$this->init();
|
|
$st = $this->PDO->prepare('SELECT name, value FROM data WHERE class=?');
|
$st->execute(array($this->class));
|
$rows = $st->fetchAll(PDO::FETCH_ASSOC);
|
|
$return = array();
|
foreach($rows as $row) {
|
$return[$row['name']] = unserialize($row['value']);
|
}
|
|
return $return;
|
}
|
|
/**
|
* Usuwa wszystkie zmienne sesyjne danego modułu.
|
*/
|
public function truncate() {
|
$this->init();
|
|
$st = $this->PDO->prepare('DELETE FROM data WHERE class=?');
|
$st->execute(array($this->class));
|
}
|
|
/**
|
* Aktualizuje schemat bazy danych oraz dane, w szczególności poprawia błędy
|
* wprowadzone we wcześniejszych wersjach (np. brak ustawionej nazwy klasy).
|
*/
|
private function updateDatabase() {
|
$st = $this->PDO->query('SELECT value FROM data WHERE class=\'\' AND name=\'_version\'');
|
$row = $st->fetch(PDO::FETCH_ASSOC);
|
|
$version = 0;
|
if (is_array($row)) {
|
$version = (int)$row['value'];
|
}
|
|
$st->closeCursor();
|
|
switch($version) {
|
case 1:
|
$this->PDO->query('UPDATE data SET class=\'kino\' WHERE class=\'\' AND name=\'kino\'');
|
$this->PDO->query('INSERT OR REPLACE INTO data (class, name, value) VALUES (\'\', \'_version\', 1)');
|
case 2:
|
case 3:
|
$this->PDO->query('DELETE FROM data WHERE class IS NULL AND name=\'user_struct\'');
|
$this->PDO->query('INSERT OR REPLACE INTO data (class, name, value) VALUES (\'\', \'_version\', 4)');
|
break;
|
}
|
}
|
|
/**
|
* Tworzy schemat bazy danych sesyjnych.
|
*/
|
private function createSchema() {
|
$this->PDO->query(
|
'CREATE TABLE data (
|
class VARCHAR(50) NOT NULL DEFAULT \'\',
|
name VARCHAR(40) NOT NULL,
|
value TEXT NOT NULL,
|
PRIMARY KEY (
|
class ASC,
|
name ASC
|
)
|
)'
|
);
|
$this->PDO->query('INSERT INTO data (class, name, value) VALUES (\'\', \'_version\', 4)');
|
}
|
|
/**
|
* Importuje dane użytkowników z poprzedniej wersji bota.
|
*/
|
private function importLegacyData() {
|
$userData = parse_url($this->user);
|
$files = glob($this->legacySessionDir.'/*/'.$userData['user'].'.ggdb');
|
if(!$files) {
|
return;
|
}
|
|
$this->PDO->beginTransaction();
|
$st = $this->PDO->prepare('INSERT OR REPLACE INTO data (class, name, value) VALUES (?, ?, ?)');
|
|
foreach($files as $file) {
|
$data = unserialize(file_get_contents($file));
|
foreach($data as $name => $value) {
|
$st->execute(array($this->class, $name, serialize($value)));
|
}
|
}
|
$this->PDO->commit();
|
|
foreach($files as $file) {
|
unlink($file);
|
}
|
}
|
}
|