ILIAS  release_8 Revision v8.19
All Data Structures Namespaces Files Functions Variables Modules Pages
class.ilDBUpdate.php
Go to the documentation of this file.
1 <?php
2 
3 declare(strict_types=1);
28 {
29  public string $DB_UPDATE_FILE;
30  public ?int $currentVersion = null;
31  public ?int $fileVersion = null;
32  public string $updateMsg;
33  protected ?ilIniFile $client_ini = null;
34  protected ?int $custom_updates_current_version = 0;
35  protected ?int $custom_updates_file_version = null;
36  protected ?bool $custom_updates_info_read = null;
37  protected string $error;
38  protected string $PATH = './';
39  protected ilDBInterface $db;
40  protected string $current_file;
41  protected string $LAST_UPDATE_FILE;
42  protected array $filecontent;
43  protected array $lastfilecontent;
44  protected int $db_update_running;
45  protected int $hotfix_current_version;
47  protected array $hotfix_version;
48  protected array $hotfix_content;
49  protected int $hotfix_file_version;
51  protected array $custom_updates_content;
53 
54  public function __construct(ilDBInterface $a_db_handler, ilIniFile $client_ini = null)
55  {
56  // workaround to allow setup migration
57  $this->client_ini = $client_ini;
58  $this->db = $a_db_handler;
59  $this->PATH = "./";
60 
61  $this->getCurrentVersion();
62 
63  // get update file for current version
64  $updatefile = $this->getFileForStep($this->currentVersion + 1);
65 
66  $this->current_file = $updatefile;
67  $this->DB_UPDATE_FILE = $this->PATH . "setup/sql/" . $updatefile;
68 
69  //
70  // NOTE: IF YOU SET THIS TO THE NEWEST FILE, CHANGE ALSO getFileForStep()
71  //
72  $this->LAST_UPDATE_FILE = $this->PATH . "setup/sql/dbupdate_05.php";
73 
74  $this->readDBUpdateFile();
75  $this->readLastUpdateFile();
76  $this->readFileVersion();
77 
78  $class_map = require ILIAS_ABSOLUTE_PATH . '/libs/composer/vendor/composer/autoload_classmap.php';
79  $this->ctrl_structure_iterator = new ilCtrlArrayIterator($class_map);
80  }
81 
85  public function getFileForStep(int $a_version): string
86  {
87  //
88  // NOTE: IF YOU ADD A NEW FILE HERE, CHANGE ALSO THE CONSTRUCTOR
89  //
90  switch (true) {
91  case ($a_version > 5431): // last number in previous file
92  return "dbupdate_05.php";
93  case ($a_version > 4182): // last number in previous file
94  return "dbupdate_04.php";
95  case ($a_version > 2948): // last number in previous file
96  return "dbupdate_03.php";
97  case ($a_version > 864): // last number in previous file
98  return "dbupdate_02.php";
99  default:
100  return "dbupdate.php";
101  }
102  }
103 
104  public function initStep(int $i): void
105  {
106  //
107  }
108 
109  public function readDBUpdateFile(): bool
110  {
111  if (!file_exists($this->DB_UPDATE_FILE)) {
112  $this->error = "no_db_update_file";
113  $this->filecontent = array();
114 
115  return false;
116  }
117 
118  $this->filecontent = @file($this->DB_UPDATE_FILE);
119 
120  return true;
121  }
122 
123  public function readLastUpdateFile(): bool
124  {
125  if (!file_exists($this->LAST_UPDATE_FILE)) {
126  $this->error = "no_last_update_file";
127  $this->lastfilecontent = array();
128 
129  return false;
130  }
131 
132  $this->lastfilecontent = @file($this->LAST_UPDATE_FILE);
133 
134  return true;
135  }
136 
137  public function getCurrentVersion(): int
138  {
139  $set = new ilSetting("common", true);
140  $this->currentVersion = (int) $set->get("db_version");
141 
142  return $this->currentVersion;
143  }
144 
145  public function setCurrentVersion(int $a_version): void
146  {
147  $set = new ilSetting("common", true);
148  $set->set("db_version", (string) $a_version);
149  $this->currentVersion = $a_version;
150  }
151 
156  public function setRunningStatus(int $a_nr): void
157  {
158  $set = new ilSetting("common", true);
159  $set->set("db_update_running", (string) $a_nr);
160  $this->db_update_running = $a_nr;
161  }
162 
167  public function getRunningStatus(): int
168  {
169  $set = new ilSetting("common", true);
170  $this->db_update_running = (int) $set->get("db_update_running");
171 
173  }
174 
178  public function clearRunningStatus(): void
179  {
180  $set = new ilSetting("common", true);
181  $set->set("db_update_running", "0");
182  $this->db_update_running = 0;
183  }
184 
185  public function readFileVersion(): int
186  {
187  //go through filecontent and search for last occurence of <#x>
188  reset($this->lastfilecontent);
189  $regs = array();
190  $version = 0;
191  foreach ($this->lastfilecontent as $row) {
192  if (preg_match('/^<\#([0-9]+)>/', $row, $regs)) {
193  $version = $regs[1];
194  }
195  }
196 
197  $this->fileVersion = (int) $version;
198 
199  return $this->fileVersion;
200  }
201 
205  public function getFileVersion(): ?int
206  {
207  return $this->fileVersion;
208  }
209 
214  public function execQuery(ilDBInterface $db, string $str): bool
215  {
216  $q = "";
217  $sql = explode("\n", trim($str));
218  foreach ($sql as $i => $statement) {
219  $sql[$i] = trim($statement);
220  if ($statement !== "" && $statement[0] !== "#") {
221  //take line per line, until last char is ";"
222  if (substr($statement, -1) === ";") {
223  //query is complete
225  $q .= " " . substr($statement, 0, -1);
226  $check = $this->checkQuery($q);
227  if ($check === true) {
228  $db->query($q);
229  } else {
230  $this->error = (string) $check;
231  return false;
232  }
233  unset($q);
234  } //if
235  else {
237  $q .= " " . $statement;
238  } //else
239  } //if
240  } //for
241  if (isset($q) && $q !== "") {
242  echo "incomplete_statement: " . $q . "<br>";
243 
244  return false;
245  }
246 
247  return true;
248  }
249 
253  public function checkQuery(string $q): bool
254  {
255  return true;
256  }
257 
259  ?ilCtrlStructureReader &$ilCtrlStructureReader,
261  ): void {
262  global $DIC;
263 
264  // TODO: There is currently a huge mixup of globals, $DIC and dependencies, esprecially in setup and during DB-Updates. This leads to many problems. The following core tries to provide the needed dependencies for the dbupdate-script. The code hopefully will change in the future.
265 
266  if (isset($GLOBALS['ilCtrlStructureReader'])) {
267  $ilCtrlStructureReader = $GLOBALS['ilCtrlStructureReader'];
268  } elseif ($DIC->offsetExists('ilCtrlStructureReader')) {
269  $ilCtrlStructureReader = $DIC['ilCtrlStructureReader'];
270  } else {
271  $ilCtrlStructureReader = new ilCtrlStructureReader(
272  $this->ctrl_structure_iterator,
274  );
275  $DIC->offsetSet('ilCtrlStructureReader', $ilCtrlStructureReader);
276  }
277 
278  $GLOBALS['ilCtrlStructureReader'] = $ilCtrlStructureReader;
279 
280  if ($this->client_ini) {
281  $ilCtrlStructureReader->setIniFile($this->client_ini);
282  }
283  $ilDB = $DIC->database();
284  }
285 
290  public function applyUpdate(int $a_break = 0)
291  {
292  $ilCtrlStructureReader = null;
293  $ilDB = null;
294  $this->initGlobalsRequiredForUpdateSteps($ilCtrlStructureReader, $ilDB);
295 
298 
299  if ($a_break > $this->currentVersion
300  && $a_break < $this->fileVersion
301  ) {
302  $f = $a_break;
303  }
304 
305  if ($c < $f) {
306  $msg = array();
307  for ($i = ($c + 1); $i <= $f; $i++) {
308  // check wether next update file must be loaded
309  if ($this->current_file != $this->getFileForStep($i)) {
310  $this->DB_UPDATE_FILE = $this->PATH . "setup/sql/" . $this->getFileForStep($i);
311  $this->readDBUpdateFile();
312  }
313 
314  $this->initStep($i);
315 
316  if ($this->applyUpdateNr($i) === false) {
317  $msg[] = "msg: update_error - " . $this->error . "; nr: " . $i . ";";
318  $this->updateMsg = implode("\n", $msg);
319 
320  return false;
321  }
322 
323  $msg[] = "msg: update_applied; nr: " . $i . ";";
324  }
325 
326  $this->updateMsg = implode("\n", $msg);
327  } else {
328  $this->updateMsg = "no_changes";
329  }
330 
331  if ($f < $this->fileVersion) {
332  return true;
333  }
334  }
335 
341  public function applyUpdateNr(int $nr, $hotfix = false, $custom_update = false): bool
342  {
343  $ilCtrlStructureReader = null;
344  $ilMySQLAbstraction = null;
345  $ilDB = null;
346  $this->initGlobalsRequiredForUpdateSteps($ilCtrlStructureReader, $ilDB);
347 
348  //search for desired $nr
349  reset($this->filecontent);
350 
351  if (!$hotfix && !$custom_update) {
352  $this->setRunningStatus($nr);
353  }
354 
355  //init
356  $i = 0;
357 
358  //go through filecontent
359  while (!preg_match("/^<\#" . $nr . ">/", $this->filecontent[$i]) && $i < count($this->filecontent)) {
360  $i++;
361  }
362 
363  //update not found
364  if ($i === count($this->filecontent)) {
365  $this->error = "update_not_found";
366 
367  return false;
368  }
369 
370  $i++;
371 
372  //update found, now extract this update to a new array
373  $update = array();
374  while ($i < count($this->filecontent) && !preg_match("/^<#" . ($nr + 1) . ">/", $this->filecontent[$i])) {
375  $update[] = trim($this->filecontent[$i]);
376  $i++;
377  }
378 
379  //now you have the update, now process it
380  $sql = array();
381  $php = array();
382  $mode = "sql";
383 
384  foreach ($update as $row) {
385  if (preg_match("/<\?php/", $row)) {
386  if (count($sql) > 0) {
387  if ($this->execQuery($this->db, implode("\n", $sql)) === false) {
388  return false;
389  }
390  $sql = array();
391  }
392  $mode = "php";
393  } elseif (preg_match("/\?>/", $row)) {
394  if (count($php) > 0) {
395  $code = implode("\n", $php);
396  if (eval($code) === false) {
397  $this->error = "Parse error: " . $code;
398 
399  return false;
400  }
401  $php = array();
402  }
403  $mode = "sql";
404  } else {
405  if ($mode === "sql") {
406  $sql[] = $row;
407  }
408 
409  if ($mode === "php") {
410  $php[] = $row;
411  }
412  } //else
413  } //foreach
414 
415  if ($mode === "sql" && count($sql) > 0) {
416  if ($this->execQuery($this->db, implode("\n", $sql)) === false) {
417  $this->error = "dump_error: " . $this->error;
418 
419  return false;
420  }
421  }
422 
423  //increase db_Version number
424  if (!$hotfix && !$custom_update) {
425  $this->setCurrentVersion($nr);
426  } elseif ($hotfix) {
427  $this->setHotfixCurrentVersion($nr);
428  } elseif ($custom_update) {
429  $this->setCustomUpdatesCurrentVersion($nr);
430  }
431 
432  if (!$hotfix && !$custom_update) {
433  $this->clearRunningStatus();
434  }
435 
436  //$this->currentVersion = $ilias->getSetting("db_version");
437 
438  return true;
439  }
440 
441  public function getDBVersionStatus(): bool
442  {
443  return !($this->fileVersion > $this->currentVersion);
444  }
445 
449  public function getTables(): array
450  {
451  $a = array();
452 
453  $query = "SHOW TABLES";
454  $res = $this->db->query($query);
455  while ($row = $res->fetchRow()) {
456  $status = $this->getTableStatus($row[0]);
457  $a[] = array("name" => $status["Table"],
458  "table" => $row[0],
459  "status" => $status["Msg_text"],
460  );
461  }
462 
463  return $a;
464  }
465 
469  public function getTableStatus(string $table)
470  {
471  $query = "ANALYZE TABLE " . $table;
472  return $this->db->query($query)->fetchRow(ilDBConstants::FETCHMODE_ASSOC);
473  }
474 
475 
479 
482  public function getHotfixCurrentVersion(): ?int
483  {
484  $this->readHotfixInfo();
485 
486  return $this->hotfix_current_version ?? null;
487  }
488 
492  public function setHotfixCurrentVersion(int $a_version): bool
493  {
494  $this->readHotfixInfo();
495  $this->hotfix_setting->set(
496  "db_hotfixes_" . $this->hotfix_version[0],
497  (string) $a_version
498  );
499  $this->hotfix_current_version = $a_version;
500 
501  return true;
502  }
503 
507  public function getHotfixFileVersion(): ?int
508  {
509  $this->readHotfixInfo();
510 
511  return $this->hotfix_file_version ?? null;
512  }
513 
517  public function readHotfixFileVersion(array $a_file_content): int
518  {
519  //go through filecontent and search for last occurence of <#x>
520  reset($a_file_content);
521  $regs = [];
522  $version = '';
523  foreach ($a_file_content as $row) {
524  if (preg_match("/^<#([0-9]+)>/", $row, $regs)) {
525  $version = $regs[1];
526  }
527  }
528 
529  return (int) $version;
530  }
531 
535  public function readHotfixInfo(bool $a_force = false): void
536  {
537  if (isset($this->hotfix_info_read) && $this->hotfix_info_read && !$a_force) {
538  return;
539  }
540  $this->hotfix_setting = new ilSetting("common", true);
541  $ilias_version = ILIAS_VERSION_NUMERIC;
542  $version_array = explode(".", $ilias_version);
543  $this->hotfix_version[0] = $version_array[0];
544  $this->hotfix_version[1] = $version_array[1];
545  $hotfix_file = $this->PATH . "setup/sql/" . $this->hotfix_version[0] . "_hotfixes.php";
546  if (is_file($hotfix_file)) {
547  $this->hotfix_content = @file($hotfix_file);
548  $this->hotfix_current_version = (int) $this->hotfix_setting->get(
549  "db_hotfixes_" . $this->hotfix_version[0]
550  );
551  $this->hotfix_file_version = $this->readHotfixFileVersion($this->hotfix_content);
552  }
553  $this->hotfix_info_read = true;
554  }
555 
559  public function hotfixAvailable(): bool
560  {
561  $this->readHotfixInfo();
562  return isset($this->hotfix_file_version) && $this->hotfix_file_version > $this->hotfix_current_version;
563  }
564 
568  public function applyHotfix(): bool
569  {
570  $ilCtrlStructureReader = null;
571  $ilDB = null;
572  $this->initGlobalsRequiredForUpdateSteps($ilCtrlStructureReader, $ilDB);
573  $this->readHotfixInfo(true);
574 
575  $f = $this->getHotfixFileVersion();
576  $c = $this->getHotfixCurrentVersion();
577 
578  if ($c < $f) {
579  $msg = array();
580  for ($i = ($c + 1); $i <= $f; $i++) {
581  $this->filecontent = $this->hotfix_content;
582 
583  if ($this->applyUpdateNr($i, true) === false) {
584  $msg[] = array("msg" => "update_error: " . $this->error,
585  "nr" => $i,
586  );
587  $this->updateMsg = implode("\n", $msg);
588 
589  return false;
590  }
591 
592  $msg[] = array("msg" => "hotfix_applied",
593  "nr" => $i,
594  );
595  }
596 
597  $this->updateMsg = implode("\n", $msg);
598  } else {
599  $this->updateMsg = "no_changes";
600  }
601 
602  return true;
603  }
604 
606  {
607  $this->readCustomUpdatesInfo();
608 
610  }
611 
612  public function setCustomUpdatesCurrentVersion(?int $a_version): bool
613  {
614  $this->readCustomUpdatesInfo();
615  $this->custom_updates_setting->set('db_version_custom', (string) $a_version);
616  $this->custom_updates_current_version = $a_version;
617 
618  return true;
619  }
620 
621  public function getCustomUpdatesFileVersion(): ?int
622  {
623  $this->readCustomUpdatesInfo();
624 
626  }
627 
628  public function readCustomUpdatesFileVersion(array $a_file_content): int
629  {
630  //go through filecontent and search for last occurence of <#x>
631  reset($a_file_content);
632  $regs = [];
633  $version = '';
634  foreach ($a_file_content as $row) {
635  if (preg_match("/^<#([0-9]+)>/", $row, $regs)) {
636  $version = $regs[1];
637  }
638  }
639 
640  return (int) $version;
641  }
642 
643  public function readCustomUpdatesInfo(bool $a_force = false): void
644  {
645  if ($this->custom_updates_info_read && !$a_force) {
646  return;
647  }
648 
649  $this->custom_updates_setting = new ilSetting();
650  $custom_updates_file = $this->PATH . "setup/sql/dbupdate_custom.php";
651  if (is_file($custom_updates_file)) {
652  $this->custom_updates_content = @file($custom_updates_file);
653  $this->custom_updates_current_version = (int) $this->custom_updates_setting->get('db_version_custom', "0");
654  $this->custom_updates_file_version = $this->readCustomUpdatesFileVersion($this->custom_updates_content);
655  }
656  $this->custom_updates_info_read = true;
657  }
658 
659  public function customUpdatesAvailable(): bool
660  {
661  $this->readCustomUpdatesInfo();
662  return $this->custom_updates_file_version > $this->custom_updates_current_version;
663  }
664 
665  public function applyCustomUpdates(): bool
666  {
667  $ilCtrlStructureReader = null;
668  $ilDB = null;
669  $this->initGlobalsRequiredForUpdateSteps($ilCtrlStructureReader, $ilDB);
670  $this->readCustomUpdatesInfo(true);
671 
672  $f = $this->getCustomUpdatesFileVersion();
674 
675  if ($c < $f) {
676  $msg = array();
677  for ($i = ($c + 1); $i <= $f; $i++) {
678  $this->filecontent = $this->custom_updates_content;
679 
680  if ($this->applyUpdateNr($i, false, true) === false) {
681  $msg[] = array("msg" => "update_error: " . $this->error,
682  "nr" => $i,
683  );
684  $this->updateMsg = implode("\n", $msg);
685 
686  return false;
687  }
688 
689  $msg[] = array("msg" => "custom_update_applied",
690  "nr" => $i,
691  );
692  }
693 
694  $this->updateMsg = implode("\n", $msg);
695  } else {
696  $this->updateMsg = "no_changes";
697  }
698 
699  return true;
700  }
701 
706  public function getUpdateSteps(int $a_break = 0): string
707  {
708  $ilCtrlStructureReader = null;
709  $ilMySQLAbstraction = null;
710  $ilDB = null;
711  $this->initGlobalsRequiredForUpdateSteps($ilCtrlStructureReader, $ilDB);
712 
713  $str = "";
714 
717 
718  if ($a_break > $this->currentVersion
719  && $a_break < $this->fileVersion
720  ) {
721  $f = $a_break;
722  }
723 
724  if ($c < $f) {
725  for ($i = ($c + 1); $i <= $f; $i++) {
726  // check wether next update file must be loaded
727  if ($this->current_file != $this->getFileForStep($i)) {
728  $this->DB_UPDATE_FILE = $this->PATH . "setup/sql/" . $this->getFileForStep($i);
729  $this->readDBUpdateFile();
730  }
731 
732  $str .= $this->getUpdateStepNr($i);
733  }
734  }
735 
736  return $str;
737  }
738 
743  public function getHotfixSteps(): string
744  {
745  $this->readHotfixInfo(true);
746 
747  $str = "";
748 
749  $f = $this->getHotfixFileVersion();
750  $c = $this->getHotfixCurrentVersion();
751 
752  if ($c < $f) {
753  for ($i = ($c + 1); $i <= $f; $i++) {
754  $this->filecontent = $this->hotfix_content;
755 
756  $str .= $this->getUpdateStepNr($i, true);
757  }
758  }
759 
760  return $str;
761  }
762 
766  public function getUpdateStepNr(int $nr, bool $hotfix = false, bool $custom_update = false): string
767  {
768  $str = "";
769 
770  //search for desired $nr
771  reset($this->filecontent);
772 
773  //init
774  $i = 0;
775 
776  //go through filecontent
777  while (!preg_match("/^<#" . $nr . ">/", $this->filecontent[$i]) && $i < count($this->filecontent)) {
778  $i++;
779  }
780 
781  //update not found
782  if ($i === count($this->filecontent)) {
783  return '';
784  }
785 
786  $i++;
787  while ($i < count($this->filecontent) && !preg_match("/^<#" . ($nr + 1) . ">/", $this->filecontent[$i])) {
788  $str .= $this->filecontent[$i];
789  $i++;
790  }
791 
792  return "<pre><b><#" . $nr . "></b>\n" . htmlentities($str) . "</pre>";
793  }
794 }
$res
Definition: ltiservices.php:69
getFileForStep(int $a_version)
Get db update file name for db step.
readCustomUpdatesFileVersion(array $a_file_content)
applyUpdate(int $a_break=0)
Apply update.
getTableStatus(string $table)
$c
Definition: cli.php:38
const ILIAS_VERSION_NUMERIC
array $hotfix_version
getHotfixSteps()
Get hotfix steps.
readHotfixInfo(bool $a_force=false)
Get status of hotfix file.
__construct(ilDBInterface $a_db_handler, ilIniFile $client_ini=null)
Class ilCtrlStructureCidGenerator.
getHotfixFileVersion()
Get current hotfix version.
getUpdateStepNr(int $nr, bool $hotfix=false, bool $custom_update=false)
Get single update step for presentation.
setCurrentVersion(int $a_version)
$update
Definition: imgupload.php:92
int $hotfix_current_version
ilSetting $custom_updates_setting
applyHotfix()
Apply hotfix.
const PATH
Definition: proxy_ylocal.php:8
array $custom_updates_content
applyUpdateNr(int $nr, $hotfix=false, $custom_update=false)
apply an update
checkQuery(string $q)
check query
Class ilCtrlStructureReader is responsible for reading ilCtrl&#39;s control structure.
global $DIC
Definition: feed.php:28
array $hotfix_content
getUpdateSteps(int $a_break=0)
Get update steps as string (for presentation)
array $lastfilecontent
string $LAST_UPDATE_FILE
setRunningStatus(int $a_nr)
Set running status for a step.
readCustomUpdatesInfo(bool $a_force=false)
int $custom_updates_file_version
Iterator $ctrl_structure_iterator
string $current_file
int $custom_updates_current_version
execQuery(ilDBInterface $db, string $str)
execute a query
bool $custom_updates_info_read
getHotfixCurrentVersion()
Get current hotfix version.
if(!defined('PATH_SEPARATOR')) $GLOBALS['_PEAR_default_error_mode']
Definition: PEAR.php:64
setHotfixCurrentVersion(int $a_version)
Set current hotfix version.
query(string $query)
Run a (read-only) Query on the database.
Class ilCtrlArrayIterator.
$query
initGlobalsRequiredForUpdateSteps(?ilCtrlStructureReader &$ilCtrlStructureReader, ?ilDBInterface &$ilDB)
int $hotfix_file_version
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
setCustomUpdatesCurrentVersion(?int $a_version)
getFileVersion()
Get Version of file.
ilDBInterface $db
hotfixAvailable()
Get status of hotfix file.
$a
thx to https://mlocati.github.io/php-cs-fixer-configurator for the examples
$check
Definition: buildRTE.php:81
getRunningStatus()
Get running status.
getCustomUpdatesCurrentVersion()
string $DB_UPDATE_FILE
clearRunningStatus()
Clear running status.
ilIniFile $client_ini
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
ilSetting $hotfix_setting
$version
Definition: plugin.php:24
$i
Definition: metadata.php:41
readHotfixFileVersion(array $a_file_content)
Set current hotfix version.