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); } } }