Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
18.99% covered (danger)
18.99%
49 / 258
15.00% covered (danger)
15.00%
3 / 20
CRAP
0.00% covered (danger)
0.00%
0 / 1
InitDAO
18.99% covered (danger)
18.99%
49 / 258
15.00% covered (danger)
15.00%
3 / 20
1073.17
0.00% covered (danger)
0.00%
0 / 1
 createSampleLoginsReviewsLogs
0.00% covered (danger)
0.00%
0 / 52
0.00% covered (danger)
0.00%
0 / 1
2
 createSampleExpiredSessions
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
2
 createSampleWorkspaceAdmins
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 createSampleMonitorSessions
0.00% covered (danger)
0.00%
0 / 49
0.00% covered (danger)
0.00%
0 / 1
2
 createAdmin
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 createWorkspace
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 addWorkspacesToAdmin
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
2
 clearDB
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
3
 cloneDB
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 getDbStatus
0.00% covered (danger)
0.00%
0 / 22
0.00% covered (danger)
0.00%
0 / 1
42
 getTableStatus
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
 createSampleCommands
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 importScanImage
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 adminExists
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 createWorkspaceIfMissing
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
6
 installPatches
80.65% covered (warning)
80.65%
25 / 31
0.00% covered (danger)
0.00%
0 / 1
9.59
 setDBSchemaVersion
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
3
 getDBContentDump
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
20
 createSampleMetaData
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 checkSQLMode
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3/** @noinspection PhpUnhandledExceptionInspection */
4declare(strict_types=1);
5// TODO unit tests
6/* TODO refactor: this should not *be* a DAO, because it initializes several DAOs itself, it should
7    - inherit from the controller maybe instead of DAO
8    - can be merged with WorkspaceInitializer maybe (since files and db management is not separable anymore anyways)
9*/
10
11class InitDAO extends SessionDAO {
12  const legacyTableNames = [
13    'admintokens',
14    'persons',
15    'logins',
16    'bookletlogs',
17    'bookletreviews',
18    'booklets',
19    'unitlogs',
20    'unitreviews'
21  ];
22
23  public function createSampleLoginsReviewsLogs(): void {
24    $timestamp = TimeStamp::now();
25
26    $sessionDAO = new SessionDAO();
27    $testDAO = new TestDAO();
28
29    $testLogin = new Login(
30      'test',
31      Password::encrypt('user123', $this->passwordSalt),
32      'run-hot-return',
33      'sample_group',
34      'sample_group',
35      ['xxx' => ['BOOKLET.SAMPLE-1']],
36      1,
37      TimeStamp::fromXMLFormat('1/1/2030 12:00'),
38      0,
39      0,
40      (object) ['somStr' => 'someLabel']
41    );
42    $loginSession = $sessionDAO->createLoginSession($testLogin);
43
44    $personSession = $sessionDAO->createOrUpdatePersonSession($loginSession, 'xxx');
45    $test = $testDAO->createTest($personSession->getPerson()->getId(), 'BOOKLET.SAMPLE-1', 'Sample Booklet 1');
46    $testDAO->setTestRunning($test->id);
47    $testDAO->addTestReview(
48      $test->id,
49      1,
50      "content",
51      "luigi: sample booklet review",
52      'Firefox/126.0'
53    );
54    $testDAO->addUnitReview(
55      $test->id,
56      "UNIT.SAMPLE",
57      1,
58      "content",
59      "mario: this is a sample unit review",
60      'Firefox/126.0',
61      "UNIT.SAMPLE",
62      1,
63      'page-1',
64    );
65    $testDAO->addUnitLog($test->id, 'UNIT.SAMPLE', "sample unit log", $timestamp);
66    $testDAO->addTestLog($test->id, "sample log entry", $timestamp);
67    $testDAO->updateDataParts(
68      $test->id,
69      'UNIT.SAMPLE',
70      ["all" => "{\"name\":\"Sam Sample\",\"age\":34}"],
71      "example-data-format",
72      $timestamp
73    );
74    $testDAO->updateUnitState($test->id, "UNIT.SAMPLE", ["PRESENTATIONCOMPLETE" => "yes"]);
75    $testDAO->updateTestState($test->id, ["CURRENT_UNIT_ID" => "UNIT.SAMPLE"]);
76    $test2 = $testDAO->createTest($personSession->getPerson()->getId(), 'BOOKLET.SAMPLE-2', 'Sample Booklet 2');
77    $testDAO->lockTest($test2->id);
78    $testDAO->setTestRunning($test2->id);
79  }
80
81  public function createSampleExpiredSessions(): void {
82    $login = new Login(
83      'test-expired',
84      '',
85      'run-hot-return',
86      'expired_group',
87      'expired_group',
88      ['xxx' => ['BOOKLET.SAMPLE-1']],
89      1,
90      TimeStamp::fromXMLFormat('1/1/2000 12:00')
91    );
92    $login = $this->createLoginSession($login);
93    $this->createOrUpdatePersonSession($login, 'xxx', true);
94  }
95
96  public function createSampleWorkspaceAdmins(): void {
97    $superAdminDAO = new SuperAdminDAO();
98    $adminDAO = new AdminDAO();
99    $superAdminDAO->createUser("workspace_admin", "anotherPassword");
100    $adminDAO->createAdminToken("workspace_admin", "anotherPassword", TimeStamp::fromXMLFormat('1/1/2000 12:00'));
101    $superAdminDAO->createUser("expired_user", "whatever", true);
102    $adminDAO->createAdminToken("expired_user", "whatever", TimeStamp::fromXMLFormat('1/1/2000 12:00'));
103  }
104
105  public function createSampleMonitorSessions(): array {
106    $personsSessions = [];
107
108    $login = new Login(
109      'test-group-monitor',
110      'user123',
111      'monitor-group',
112      'sample_group',
113      'sample_group',
114      [],
115      1,
116      TimeStamp::fromXMLFormat('1/1/2030 12:00')
117    );
118    $loginSession = $this->createLoginSession($login);
119    $personsSessions['test-group-monitor'] = $this->createOrUpdatePersonSession($loginSession, '');
120
121    $login = new Login(
122      'expired-group-monitor',
123      'user123',
124      'monitor-group',
125      'expired_group',
126      'expired_group',
127      ['' => ['']],
128      1,
129      TimeStamp::fromXMLFormat('1/1/2000 12:00')
130    );
131    $loginSession = $this->createLoginSession($login);
132    $personsSessions['expired-group-monitor'] = $this->createOrUpdatePersonSession($loginSession, '', true);
133
134    $login = new Login(
135      'test-study-monitor',
136      'user123',
137      'monitor-study',
138      'study_group',
139      "A group for the study monitor",
140      [],
141      1
142    );
143    $loginSession = $this->createLoginSession($login);
144    $personsSessions['test-study-monitor'] = $this->createOrUpdatePersonSession($loginSession, '');
145
146    $login = new Login(
147      'expired-study-monitor',
148      'user123',
149      'monitor-study',
150      'expired_group',
151      'expired_group',
152      ['' => ['']],
153      1,
154      TimeStamp::fromXMLFormat('1/1/2000 12:00')
155    );
156    $loginSession = $this->createLoginSession($login);
157    $personsSessions['expired-study-monitor'] = $this->createOrUpdatePersonSession($loginSession, '', true);
158
159    return $personsSessions;
160  }
161
162  public function createAdmin(string $username, string $password): int {
163    $superAdminDAO = new SuperAdminDAO();
164    $admin = $superAdminDAO->createUser($username, $password, true);
165    $adminDAO = new AdminDAO();
166    $adminDAO->createAdminToken($username, $password); // TODO why?
167    return (int) $admin['id'];
168  }
169
170  public function createWorkspace(string $workspaceName): int {
171    $superAdminDAO = new SuperAdminDAO();
172    $workspace = $superAdminDAO->createWorkspace($workspaceName);
173    return (int) $workspace['id'];
174  }
175
176  public function addWorkspacesToAdmin(int $adminId, array $workspaceIds): void {
177    $superAdminDAO = new SuperAdminDAO();
178    $superAdminDAO->setWorkspaceRightsByUser(
179      $adminId,
180      array_map(
181        function (int $wsId) {
182          return (object) [
183            "role" => "RW",
184            "id" => $wsId
185          ];
186        },
187        $workspaceIds
188      )
189    );
190  }
191
192  public function clearDB(): array {
193    $droppedTables = [];
194
195    $this->_('SET FOREIGN_KEY_CHECKS = 0');
196
197    foreach (array_merge($this::legacyTableNames, $this::tables) as $table) {
198      if ($this->getTableStatus($table) !== 'missing') {
199        $droppedTables[] = $table;
200        $this->_("drop table $table");
201      }
202    }
203
204    $this->_('SET FOREIGN_KEY_CHECKS = 1');
205
206    return $droppedTables;
207  }
208
209  public function cloneDB(string $prodDBName): void {
210    $this->clearDB();
211
212    foreach ($this::tables as $table) {
213      $creationString = $this->_("show create table $prodDBName.$table")['Create Table'];
214      $this->_($creationString);
215      $this->_("truncate $table"); // to reset auto-increment
216    }
217  }
218
219  // TODO unit-test
220  public function getDbStatus(): array {
221    $tableStatus = [
222      'used' => [],
223      'missing' => [],
224      'empty' => []
225    ];
226
227    foreach ($this::tables as $table) {
228      $tableStatus[$this->getTableStatus($table)][] = $table;
229    }
230
231    $used = count($tableStatus['used']);
232    $missing = count($tableStatus['missing']);
233    $tables = $missing
234      ? ($missing == count($this::tables) ? 'empty' : 'incomplete')
235      : 'complete';
236
237    return [
238      'message' => $tables
239        . ". \nMissing Tables: "
240        . ($missing ? implode(', ', $tableStatus['missing']) : 'none')
241        . ". \nUsed Tables: "
242        . ($used ? implode(', ', $tableStatus['used']) : 'none')
243        . '.',
244      'used' => !!$used,
245      'tables' => $tables
246    ];
247  }
248
249  protected function getTableStatus(string $table): string {
250    try {
251      $entries = $this->_("SELECT * FROM $table limit 10", [], true);
252      return count($entries) ? 'used' : 'empty';
253
254    } catch (Exception) {
255      return 'missing';
256    }
257  }
258
259  public function createSampleCommands(int $commanderId): void {
260    $adminDAO = new AdminDAO();
261    $adminDAO->storeCommand($commanderId, 1, new Command(-1, 'COMMAND', 1597906980, 'p4'));
262    $adminDAO->storeCommand($commanderId, 1, new Command(-1, 'COMMAND', 1597906970, 'p3'));
263    $adminDAO->storeCommand($commanderId, 1, new Command(-1, 'COMMAND', 1597906960, 'p1', 'p2'));
264  }
265
266  public function importScanImage(int $workspaceId, string $imagePath): void {
267    $adminDAO = new AdminDAO();
268    $attachment = $adminDAO->getAttachmentById("$workspaceId:UNIT.SAMPLE:v2");
269    AttachmentFiles::importFiles($workspaceId, [$imagePath], $attachment, 'image');
270  }
271
272  public function adminExists(): bool {
273    $admins = $this->_("select count(*) as count from users where is_superadmin = 1");
274    return (int) $admins['count'] > 0;
275  }
276
277  public function createWorkspaceIfMissing(Workspace $workspace): array {
278    $workspaceFromDb = $this->_(
279      "select workspaces.id, workspaces.name from workspaces where `id` = :ws_id",
280      [':ws_id' => $workspace->getId()]
281    );
282
283    if ($workspaceFromDb) {
284      return $workspaceFromDb;
285    }
286
287    $name = "ws {$workspace->getId()} [restored " . TimeStamp::toSQLFormat(TimeStamp::now()) . "]";
288
289    $this->_(
290      'insert into workspaces (name, id) values (:ws_name, :ws_id)',
291      [':ws_name' => $name, ':ws_id' => $workspace->getId()]
292    );
293
294    return [
295      "name" => $name,
296      "restored" => true,
297      "id" => $workspace->getId()
298    ];
299  }
300
301  public function installPatches(string $patchesDir, bool $allowFailing): array {
302    $report = [
303      'patches' => [],
304      'errors' => []
305    ];
306
307    $patches = array_map(
308      function ($file) {
309        return basename($file, '.sql');
310      },
311      Folder::glob($patchesDir, '*.sql')
312    );
313    usort($patches, [Version::class, 'compare']);
314
315    $nextPatchAvailable = ($patches[0] == 'next');
316    if ($nextPatchAvailable) {
317      $patches[] = array_shift($patches);
318    }
319
320    $patchIsFutureVersion = true;
321
322    foreach ($patches as $patch) {
323      $lastWasFutureVersion = $patchIsFutureVersion;
324      $patchIsFutureVersion = Version::compare($patch) > 0;
325      $shouldBeInstalled = Version::compare($patch, $this->getDBSchemaVersion()) <= 0;
326      $forcePatch = ($patch == 'next') && !$lastWasFutureVersion;
327
328      if (
329        (!$forcePatch) &&
330        ($patchIsFutureVersion or $shouldBeInstalled)
331      ) {
332        continue;
333      }
334
335      try {
336        $report['patches'][] = $patch;
337        $this->runFile("$patchesDir/$patch.sql");
338        $this->setDBSchemaVersion($patch);
339
340      } catch (PDOException $exception) {
341        $report['errors'][$patch] = $exception->getMessage();
342
343        if (!$allowFailing) {
344          return $report;
345        }
346      }
347    }
348
349    return $report;
350  }
351
352  public function setDBSchemaVersion(string $newVersion): void {
353    $currentDBSchemaVersion = $this->getDBSchemaVersion();
354
355    if ($currentDBSchemaVersion == '0.0.0-no-table') {
356      return;
357    }
358
359    if ($currentDBSchemaVersion == '0.0.0-no-entry') {
360      $this->_(
361        "insert into meta (metaKey, value) values ('dbSchemaVersion', :new_version)",
362        [':new_version' => $newVersion]
363      );
364    } else {
365      $this->_(
366        "update meta set value = :new_version where metaKey = 'dbSchemaVersion'",
367        [':new_version' => $newVersion]
368      );
369    }
370  }
371
372  public function getDBContentDump(bool $includeLegacyTables = false): array {
373    $tables = $includeLegacyTables ? array_merge($this::legacyTableNames, $this::tables) : $this::tables;
374
375    $report = [];
376
377    foreach ($tables as $table) {
378      try {
379        $entries = $this->_("SELECT * FROM $table", [], true);
380        $report[$table] = CSV::build($entries);
381      } catch (Exception) {
382        $report[$table] = 'not found';
383      }
384    }
385
386    return $report;
387  }
388
389  public function createSampleMetaData(): void {
390    $this->setMeta('appConfig', 'aKey', 'newValue');
391  }
392
393  public function checkSQLMode(): bool {
394    $d = $this->_("select 'a' || 'b' as merged")['merged'];
395    return $d === 'ab';
396  }
397}