DBConnector::__construct()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 1
dl 0
loc 9
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace palma;
4
5
// Copyright (C) 2014 Universitätsbibliothek Mannheim
6
// See file LICENSE for license details.
7
8
// Authors: Alexander Wagner, Stefan Weil
9
10
class DBConnector extends \SQLite3
11
{
12
  private const SQL_CREATE_TABLES = <<< eod
13
  BEGIN EXCLUSIVE TRANSACTION;
14
  PRAGMA foreign_keys = ON;
15
16
  CREATE TABLE IF NOT EXISTS setting (
17
    key VARCHAR (10) PRIMARY KEY,
18
    value VARCHAR (20)
19
  );
20
  INSERT OR IGNORE INTO setting VALUES ('layout', 'g1x1');
21
  INSERT OR IGNORE INTO setting VALUES ('pin', '');
22
23
  CREATE TABLE IF NOT EXISTS user (
24
    userid INTEGER PRIMARY KEY,
25
    name VARCHAR (30) UNIQUE,
26
    count INTEGER,
27
    enabled INTEGER
28
  );
29
30
  -- Table with user name, IP address and device type (laptop, tablet, mobile).
31
  CREATE TABLE IF NOT EXISTS address (
32
    userid INTEGER,
33
    address VARCHAR (30),
34
    device VARCHAR (6),
35
    FOREIGN KEY(userid) REFERENCES user(userid)
36
  );
37
38
  -- userid INTEGER REFERENCES user(userid)
39
  -- The software currently does not handle this correctly, so we had to remove
40
  -- the reference because it fails with pragma foreign_keys.
41
  CREATE TABLE IF NOT EXISTS window (
42
    id INTEGER PRIMARY KEY,
43
    win_id VARCHAR (3),
44
    section INTEGER,
45
    state VARCHAR (10),
46
    file VARCHAR (255),
47
    handler VARCHAR (255),
48
    userid INTEGER,
49
    date DATETIME
50
  );
51
  END TRANSACTION;
52
  eod;
53
54
  private const SQL_RESET_TABLES = <<< eod
55
  DROP TABLE address;
56
  DROP TABLE setting;
57
  DROP TABLE IF EXISTS settings;
58
  DROP TABLE user;
59
  DROP TABLE window;
60
  eod;
61
62
  private static ?DBConnector $instance = null;
63
64
  public static function getInstance(): DBConnector
65
  {
66
    if (is_null(self::$instance)) {
67
      self::$instance = new DBConnector('palma.db');
68
    }
69
    return self::$instance;
0 ignored issues
show
Bug Best Practice introduced by
The expression return self::instance could return the type null which is incompatible with the type-hinted return palma\DBConnector. Consider adding an additional type-check to rule them out.
Loading history...
70
  }
71
72
  private function __construct(string $filename)
73
  {
74
    parent::__construct($filename);
75
76
    // Wait up to 10000 ms when the database is locked.
77
    $this->busyTimeout(10000);
78
79
    // Create any missing tables.
80
    $this->exec(self::SQL_CREATE_TABLES);
81
  }
82
83
  public function resetTables(): void
84
  {
85
    $this->exec(self::SQL_RESET_TABLES . self::SQL_CREATE_TABLES);
86
  }
87
88
  public function countWindows(): int
89
  {
90
    $numRows = $this->querySingle('SELECT count(*) FROM window WHERE state="active"');
91
    return $numRows;
92
  }
93
94
  public function nextID(): int
95
  {
96
    // Find the first unused monitor section and return its number.
97
    $quadrant_ids = array(1, 2, 3, 4);
98
    $window_db_ids = array();
99
100
    $window_keys = @$this->query('SELECT DISTINCT(id) FROM window');
101
    while ($row = $window_keys->fetchArray()) {
102
      array_push($window_db_ids, $row['id']);
103
    }
104
    $window_keys->finalize();
105
    $next_ids = array_values(array_diff($quadrant_ids, $window_db_ids));
106
    if (count($next_ids) > 0) {
107
      $next = $next_ids[0];
108
    } else {
109
      $next = $this->querySingle('SELECT MAX(id) FROM window') + 1;
110
    }
111
    return $next;
112
  }
113
114
  public function ipAddress(): string
115
  {
116
    $ip = 'unknown';
117
    if (isset($_SERVER['HTTP_X_FORWARED_FOR'])) {
118
      // Server is hidden behind a proxy.
119
      // Proxy for loopback ip might indicate spoofing
120
      if (!in_array($_SERVER['HTTP_X_FORWARED_FOR'], array('127.0.0.1', '::1'))) {
121
        $ip = $_SERVER['HTTP_X_FORWARED_FOR'];
122
      }
123
    } elseif (isset($_SERVER['REMOTE_ADDR'])) {
124
      // Client has direct access to the server.
125
      $ip = $_SERVER['REMOTE_ADDR'];
126
    }
127
    return $ip;
128
  }
129
130
  public function checkPermission(): bool
131
  {
132
    // Check whether the remote IP address is allowed to run commands.
133
    // Only localhost or hosts of registered users are allowed.
134
    $ip = $this->ipAddress();
135
    // Currently PalMA also makes local connections.
136
    $ip_list = array('127.0.0.1', '::1');
137
    $rows = $this->query("SELECT address FROM address");
138
    while ($row = $rows->fetchArray(SQLITE3_ASSOC)) {
139
      array_push($ip_list, $row['address']);
140
    }
141
    $rows->finalize();
142
    $allowed = in_array($ip, $ip_list);
143
    return $allowed;
144
  }
145
146
  public function addUser(string $username, string $address, string $device = 'laptop'): void
147
  {
148
    // Add a new user with his/her address and the device to the database.
149
    $this->exec("INSERT OR IGNORE INTO user VALUES (NULL, '$username', 1, 0)");
150
    $usercount = intval($this->querySingle("SELECT COUNT(*) FROM user"));
151
    $userid = $this->querySingle("SELECT userid from user where name='" . $username . "'");
152
    $this->exec("INSERT INTO address VALUES ('$userid', '$address', '$device')");
153
    require_once 'globals.php';
154
    debug("DBConnector::addUser: user $username connected with $device, $usercount user(s) now connected");
155
    if ($usercount == 1) {
156
      // First user connected. Always enable this person.
157
      $this->enableUser($username);
158
    }
159
  }
160
161
  public function delUser(string $username, string $address): void
0 ignored issues
show
Unused Code introduced by
The parameter $address is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

161
  public function delUser(string $username, /** @scrutinizer ignore-unused */ string $address): void

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
162
  {
163
    // Remove an existing user with his/her address from the database.
164
    $ip = $this->ipAddress();
165
    $userid = $this->querySingle("SELECT userid from user where name='$username'");
166
    if (!is_null($userid)) {
167
      // Kill VNC connection associated with the user
168
      $this->deleteVNCWindow($userid);
169
      $this->exec("DELETE FROM user WHERE userid = '$userid'");
170
    }
171
    $this->exec("DELETE FROM address WHERE userid = '$userid' AND address = '$ip'");
172
    $usercount = intval($this->querySingle("SELECT COUNT(*) FROM user"));
173
    require_once 'globals.php';
174
    debug("DBConnector::delUser: user $username disconnected, $usercount user(s) now connected");
175
    if ($usercount == 0) {
176
      // Last user disconnected.
177
      // Clean some tables, just to be sure that nothing is left.
178
      $this->exec("DELETE FROM address; DELETE FROM window;");
179
    }
180
  }
181
182
  public function enableUser(string $username): void
183
  {
184
    $state = $this->exec("UPDATE user SET enabled=1 WHERE name='$username'");
185
    if (!$state) {
186
      require_once 'globals.php';
187
      debug("DBConnector::enableUser: failed for $username");
188
    }
189
  }
190
191
  /** @return array<string> */
192
  public function getUsers(): array
193
  {
194
    $users = array();
195
    $rows = $this->query("SELECT name FROM user");
196
    while ($row = $rows->fetchArray(SQLITE3_ASSOC)) {
197
      array_push($users, $row['name']);
198
    }
199
    $rows->finalize();
200
    return $users;
201
  }
202
203
  public function checkUser(string $username): bool
204
  {
205
    $users = $this->getUsers();
206
    // empty db (e.g. after PalMA restart) or old session
207
    if (count($users) === 0 || !in_array($username, $users)) {
208
      return false;
209
    }
210
    return true;
211
  }
212
213
  /** @return array<array<string>> */
214
  public function getWindows(): array
215
  {
216
    // Get list of all windows, ordered by their section.
217
    $window_objs = array();
218
    $windows = @$this->query('SELECT * FROM window ORDER BY section ASC');
219
    while ($row = $windows->fetchArray()) {
220
      array_push($window_objs, $row);
221
    }
222
    $windows->finalize();
223
224
    return $window_objs;
225
  }
226
227
  public function getWindowIDBySection(string $section): ?string
228
  {
229
    $id = $this->querySingle("SELECT win_id FROM window WHERE section='$section'");
230
    return $id;
231
  }
232
233
  /** @return array<array<string>> */
234
  public function getVNCClientInfo(): array
235
  {
236
237
    $info = array();
238
239
    $i = @$this->query('SELECT * FROM window WHERE handler="vnc"');
240
    while ($row = $i->fetchArray()) {
241
      array_push($info, $row);
242
    }
243
    $i->finalize();
244
245
    return $info;
246
  }
247
248
  /*
249
     public function getVNC_ClientWindowIDs() {
250
     $ids = array();
251
     $rows = @$this->query('SELECT win_id FROM window WHERE handler="vnc"');
252
     while ($row = $rows->fetchArray()) {
253
     array_push($ids, $row['win_id']);
254
     }
255
     $rows->finalize();
256
     return $ids;
257
     }
258
   */
259
260
  public function getWindowState(string $window_id): string
261
  {
262
    $state = @$this->querySingle('SELECT state FROM window WHERE win_id="' . $window_id . '"');
263
    return $state;
264
  }
265
266
  public function setWindowState(string $window_id, string $state): void
267
  {
268
    $this->exec('UPDATE window SET state="' . $state . '" WHERE win_id="' . $window_id . '"');
269
  }
270
271
  /**
272
   * @param array<string> $window
273
   */
274
  public function insertWindow(array $window): void
275
  {
276
    // transfer ob complete window object/array necessary
277
    $sql = 'INSERT INTO window (id, win_id, section, state, file, handler, userid, date) ' .
278
           'VALUES ' . '("' .
279
           $window[0] . '", "' . $window[1] . '", "' .
280
           $window[2] . '", "' . $window[3] . '", "' .
281
           $window[4] . '", "' . $window[5] . '", "' .
282
           $window[6] . '", "' . $window[7] . '")';
283
    $new = $this->exec($sql);
284
    require_once 'globals.php';
285
    debug("DBConnector::insertWindow: sql=$sql, result=$new");
286
  }
287
288
  public function deleteWindow(string $window_id): void
289
  {
290
    $this->exec('DELETE FROM window WHERE win_id="' . $window_id . '"');
291
  }
292
293
  public function deleteVNCWindow(string $userid): void
294
  {
295
    $winid = $this->querySingle('SELECT win_id FROM window WHERE handler="vnc" AND userid="' . $userid . '"');
296
    if (is_null($winid)) {
297
      return;
298
    }
299
    require_once 'globals.php';
300
    wmClose($winid);
301
    $this->deleteWindow($winid);
302
    //$this->exec('DELETE FROM window WHERE handler="vnc" AND userid="'.$userid.'"');
303
  }
304
305
  public function deleteDebug(string $table, string $id, string $gt): void
306
  {
307
    $this->exec('DELETE FROM ' . $table . ' WHERE ' . $id . ' >"' . $gt . '"');
308
  }
309
310
  public function updateWindow(?string $window_id, string $field, string $value): void
311
  {
312
    if (is_null($window_id)) {
313
      return;
314
    }
315
    $this->exec('UPDATE window SET ' . $field . '="' . $value . '" WHERE win_id="' . $window_id . '"');
316
  }
317
}
318