Jacek Kowalski
2016-02-12 ddfb6ac0d4ebfebc66489f1822c6457cd0ca0a18
commit | author | age
8bd4d9 1 <?php
JK 2 /**
0868e0 3  * Klasa przechowująca dane przekazane przez użytkownika,
JK 4  * w szczególności jego ustawienia.
8bd4d9 5  */
JK 6 class BotSession {
7     /**
6d8764 8      * Instancja PDO tworzona w metodzie {@link BotSession::init()}.
JK 9      * @var PDO $PDO
10      */
11     protected $PDO;
12
13     /**
14      * Katalog, w którym trzymane są dane sesyjne użytkowników.
15      * @var string $sessionDir
16      */
17     protected $sessionDir;
18
19     /**
20      * Katalog, w którym trzymane są dane sesyjne użytkowników
21      * z poprzedniej wersji bota.
22      * @var string $legacySessionDir
23      */
24     protected $legacySessionDir;
25
26     /**
27      * Nazwa modułu (max. 40 znaków), którego zmienne klasa aktualnie przetwarza,
28      * ustawiana metodą {@link BotSession::setClass()}.
29      * @var string $class
8bd4d9 30      */
7b043b 31     protected $class = '';
8bd4d9 32     
0868e0 33     /**
JK 34      * Pseudo-URL użytkownika.
35      * @see BotUser
6d8764 36      * @var string $user
0868e0 37      */
6d8764 38     protected $user;
JK 39
0868e0 40     /**
6d8764 41      * Inicjuje klasę dla podanego użytkownika
JK 42      * @param string $user Pseudo-URL użytkownika
43      * @param string $sessionDir Katalog z danymi, domyślnie BOT_TOPDIR/database
44      * @param string $legacySessionDir Katalog z danymi ze starej wersji bota, domyślnie BOT_TOPDIR/db
0868e0 45      */
6d8764 46     public function __construct($user, $sessionDir = NULL, $legacySessionDir = NULL) {
JK 47         if(empty($sessionDir)) {
48             $sessionDir = BOT_TOPDIR.'/database';
49         }
50         if(empty($legacySessionDir)) {
51             $legacySessionDir = BOT_TOPDIR.'/db';
52         }
53
54         $this->user = $user;
55         $this->sessionDir = $sessionDir;
56         $this->legacySessionDir = $legacySessionDir;
8bd4d9 57     }
6d8764 58
JK 59     /**
60      * Sprawdza ustawienie pola {@link BotSession::$class} oraz, jeśli nie została wykonana wcześniej,
61      * dokonuje inicjalizacji klasy.
62      * Metoda ta winna być wywoływana przez każdą publiczną funkcję operującą na danych.
63      * @throws Exception Wyjątek rzucany, gdy przed użyciem metody, nazwa klasy
64      *  nie została ustawiona metodą {@link BotSession::setClass()}
65      */
66     protected function init() {
67         if(empty($this->class)) {
68             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".');
57117d 69         }
JK 70         
8bd4d9 71         if($this->PDO) {
6d8764 72             // Inicjalizacja została już przeprowadzona - wyjdź.
8bd4d9 73             return;
JK 74         }
6d8764 75
JK 76         $dbFile = $this->sessionDir.'/'.sha1(sha1($this->user)).'.sqlite';
77
78         $this->PDO = new PDO('sqlite:'.$dbFile);
79         $this->PDO->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
80         $this->PDO->setAttribute(PDO::ATTR_ORACLE_NULLS, PDO::NULL_TO_STRING);
81
82         $st = $this->PDO->query('SELECT COUNT(name) FROM sqlite_master WHERE type=\'table\' AND name=\'data\'');
83         $num = $st->fetch(PDO::FETCH_NUM);
84         $schemaExists = $num[0] > 0;
85
86         if($schemaExists) {
87             $this->updateDatabase();
88         } else {
89             try {
90                 $this->createSchema();
91                 $this->importLegacyData();
c661ee 92             }
6d8764 93             catch(Exception $e) {
JK 94                 // Import danych nie udał się - usuń pozostałości.
95                 if(file_exists($dbFile)) {
96                     @unlink($dbFile);
8bd4d9 97                 }
6d8764 98                 throw $e;
8bd4d9 99             }
JK 100         }
101     }
102     
0868e0 103     /**
JK 104      * Ustawia nazwę modułu/klasy, której zmienne będą przetwarzane
105      * @param string $class Nazwa modułu
106      */
6d8764 107     public function setClass($class) {
0868e0 108         $this->class = $class;
JK 109     }
110     
111     /**
6d8764 112      * Pobiera zmienną o podanej nazwie (getter).
JK 113      * @param string $name Nazwa zmiennej.
114      * @return mixed Wartość zmiennej lub NULL, jeśli zmienna nie istnieje.
0868e0 115      */
6d8764 116     public function __get($name) {
8bd4d9 117         $this->init();
JK 118         
119         $st = $this->PDO->prepare('SELECT value FROM data WHERE class=? AND name=?');
120         $st->execute(array($this->class, $name));
121         $st = $st->fetch(PDO::FETCH_ASSOC);
122         
123         if(is_array($st)) {
124             return unserialize($st['value']);
125         }
6d8764 126
JK 127         return NULL;
8bd4d9 128     }
JK 129     
0868e0 130     /**
6d8764 131      * Ustawia zmienną o podanej nazwie.
JK 132      * @param string $name Nazwa zmiennej.
133      * @param mixed $value Wartość do ustawienia.
0868e0 134      */
6d8764 135     public function __set($name, $value) {
8bd4d9 136         $this->init();
JK 137         
138         $st = $this->PDO->prepare('INSERT OR REPLACE INTO data (class, name, value) VALUES (?, ?, ?)');
139         $st->execute(array($this->class, $name, serialize($value)));
140     }
141     
0868e0 142     /**
JK 143      * Sprawdza czy podana zmienna została ustawiona.
6d8764 144      * @param string $name Nazwa zmiennej do sprawdzenia.
0868e0 145      * @return bool Czy zmienna istnieje?
JK 146      */
6d8764 147     public function __isset($name) {
8bd4d9 148         $this->init();
JK 149         
150         $st = $this->PDO->prepare('SELECT COUNT(name) FROM data WHERE class=? AND name=?');
151         $st->execute(array($this->class, $name));
152         $st = $st->fetch(PDO::FETCH_NUM);
153         
6d8764 154         return ($st[0] > 0);
8bd4d9 155     }
JK 156     
0868e0 157     /**
6d8764 158      * Usuwa zmienną o podanej nazwie.
JK 159      * @param string $name Nazwa zmiennej do usunięcia.
0868e0 160      */
6d8764 161     public function __unset($name) {
8bd4d9 162         $this->init();
JK 163         
164         $st = $this->PDO->prepare('DELETE FROM data WHERE class=? AND name=?');
165         $st->execute(array($this->class, $name));
166     }
167     
0868e0 168     /**
6d8764 169      * Dodaje tablicę zmiennych do danych użytkownika.
JK 170      * @param array $array Tablica zmiennych do dodania.
0868e0 171      */
6d8764 172     public function push($array) {
JK 173         $this->init();
174
8bd4d9 175         $this->PDO->beginTransaction();
JK 176         foreach($array as $name => $value) {
177             $this->__set($name, $value);
178         }
179         $this->PDO->commit();
180     }
181     
0868e0 182     /**
6d8764 183      * Zwraca wszystkie ustawione zmienne dla modułu.
JK 184      * @return array Lista wszystkich zmiennych.
0868e0 185      */
6d8764 186     public function pull() {
8bd4d9 187         $this->init();
JK 188         
189         $st = $this->PDO->prepare('SELECT name, value FROM data WHERE class=?');
190         $st->execute(array($this->class));
6d8764 191         $rows = $st->fetchAll(PDO::FETCH_ASSOC);
8bd4d9 192         
JK 193         $return = array();
6d8764 194         foreach($rows as $row) {
0868e0 195             $return[$row['name']] = unserialize($row['value']);
8bd4d9 196         }
JK 197         
198         return $return;
199     }
200     
0868e0 201     /**
JK 202      * Usuwa wszystkie zmienne sesyjne danego modułu.
203      */
6d8764 204     public function truncate() {
8bd4d9 205         $this->init();
JK 206         
207         $st = $this->PDO->prepare('DELETE FROM data WHERE class=?');
208         $st->execute(array($this->class));
209     }
6d8764 210
JK 211     /**
212      * Aktualizuje schemat bazy danych oraz dane, w szczególności poprawia błędy
213      * wprowadzone we wcześniejszych wersjach (np. brak ustawionej nazwy klasy).
214      */
215     private function updateDatabase() {
216         $st = $this->PDO->query('SELECT value FROM data WHERE class=\'\' AND name=\'_version\'');
217         $row = $st->fetch(PDO::FETCH_ASSOC);
218
219         $version = 0;
220         if (is_array($row)) {
221             $version = (int)$row['value'];
222         }
223
224         $st->closeCursor();
225
226         switch($version) {
227             case 1:
228                 $this->PDO->query('UPDATE data SET class=\'kino\' WHERE class=\'\' AND name=\'kino\'');
229                 $this->PDO->query('INSERT OR REPLACE INTO data (class, name, value) VALUES (\'\', \'_version\', 1)');
230             case 2:
231             case 3:
232                 $this->PDO->query('DELETE FROM data WHERE class IS NULL AND name=\'user_struct\'');
233                 $this->PDO->query('INSERT OR REPLACE INTO data (class, name, value) VALUES (\'\', \'_version\', 4)');
234                 break;
235         }
236     }
237
238     /**
239      * Tworzy schemat bazy danych sesyjnych.
240      */
241     private function createSchema() {
242         $this->PDO->query(
243             'CREATE TABLE data (
244                 class VARCHAR(50) NOT NULL DEFAULT \'\',
245                 name VARCHAR(40) NOT NULL,
246                 value TEXT NOT NULL,
247                 PRIMARY KEY (
248                     class ASC,
249                     name ASC
250                 )
251             )'
252         );
253         $this->PDO->query('INSERT INTO data (class, name, value) VALUES (\'\', \'_version\', 4)');
254     }
255
256     /**
257      * Importuje dane użytkowników z poprzedniej wersji bota.
258      */
259     private function importLegacyData() {
260         $userData = parse_url($this->user);
261         $files = glob($this->legacySessionDir.'/*/'.$userData['user'].'.ggdb');
262         if(!$files) {
263             return;
264         }
265
266         $this->PDO->beginTransaction();
267         $st = $this->PDO->prepare('INSERT OR REPLACE INTO data (class, name, value) VALUES (?, ?, ?)');
268
269         foreach($files as $file) {
270             $data = unserialize(file_get_contents($file));
271             foreach($data as $name => $value) {
272                 $st->execute(array($this->class, $name, serialize($value)));
273             }
274         }
275         $this->PDO->commit();
276
277         foreach($files as $file) {
278             unlink($file);
279         }
280     }
8bd4d9 281 }