YaWK  24.1
Yet another WebKit
update.php
Go to the documentation of this file.
1 <?php
2 namespace YAWK
3 {
4  use YAWK\db;
5  use YAWK\sys;
6  require_once 'sys.php';
7  /**
8  * @details YaWK System Updater Class
9  *
10  * The YaWK System Updater Class provides methods to facilitate the updating process of the system.
11  * It includes features such as checking for new versions, verifying and comparing files to ensure
12  * a stable and reliable update. This class aims to simplify the update process and minimize the
13  * risk of any errors or issues during the update.
14  *
15  * @author Daniel Retzl <[email protected]>
16  * @copyright 2009-2023 Daniel Retzl
17  * @license https://opensource.org/licenses/MIT
18  * @version 1.0.0
19  * @brief The update class - handles yawk's system update functions.
20  */
21  class update
22  {
23  /** @var string $base_dir the base directory of the YaWK installation */
24  public string $base_dir = '';
25 
26  /** @var string $updateServer the remote server to connect to */
27  public string $updateServer = 'https://update.yawk.io/';
28 
29  /** @var string $githubServer the remote GitHub server to fetch from */
30  public string $githubServer = 'https://raw.githubusercontent.com/YaWK/yawk.io/master/';
31 
32  /** @var string $updateFile contains all update information like version, build time, build message, etc. */
33  public string $updateFile = 'update.ini';
34 
35  /**
36  * @var string $currentVersion contains the current installed version (eg. 23.0.0)
37  */
38  public string $currentVersion = '';
39 
40  /**
41  * @var string $updateVersion contains the version of current update (eg. 23.1.2)
42  */
43  public string $updateVersion = '';
44 
45  /**
46  * @var string $localUpdateSystemPath the path to the local update system folder (eg. system/update/)
47  */
48  public string $localUpdateSystemPath = 'system/update/';
49 
50  /**
51  * @var string $updateFilebase the name of the remote filebase file (eg. filebase.ini)
52  */
53  public string $updateFilebase = 'filebase.ini';
54 
55  /**
56  * @var array $updateSettings contains all update settings
57  */
58  public array $updateSettings = array();
59 
60  /**
61  * @var string $updateFilesFile contains all files that need to be updated
62  */
63  public string $updateFilesFile = 'updateFiles.ini';
64 
65  /**
66  * @var array $updateFiles array of files that need to be updated
67  */
68  public array $updateFiles = array();
69 
70  /**
71  * @var bool $migrationSuccessful true|false indicates if migration was successful, call fetchFiles() on true
72  */
73  public bool $migrationSuccessful = false;
74 
75  /**
76  * @brief update constructor. Check if allow_url_fopen is enabled
77  * @details will be called by xhr request from admin/js/update-generateLocalFilebase.php
78  */
79  public function __construct()
80  {
81  // Get the value of the allow_url_fopen setting
82  $allowUrlFopen = ini_get('allow_url_fopen');
83  // Check if allow_url_fopen is enabled
84  if (!$allowUrlFopen)
85  { // allow_url_fopen is disabled, exit with error
86  // todo: syslog entry
87  echo "allow_url_fopen is disabled, but required to use the update methods. Please enable allow_url_fopen in your php.ini file or ask your admin / hoster for assistance.";
88  }
89 
90  // check if update server is reachable
91  if ($this->isServerReachable($this->updateServer.$this->updateFile) === false)
92  { // todo: syslog entry
93  echo "Update server $this->updateServer not reachable. Update not possible at the moment - please try again later.<br>";
94  }
95 
96  }
97 
98  /**
99  * @brief check if update server is reachable
100  * @details returns true if server is reachable, false if not
101  * @param $url string url to check
102  * @return bool true|false
103  */
104  public function isServerReachable(string $url): bool
105  {
106  $ch = curl_init($url);
107  curl_setopt($ch, CURLOPT_NOBODY, true);
108  curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
109  curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30);
110  curl_setopt($ch, CURLOPT_TIMEOUT, 30);
111  curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
112  curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
113  curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (compatible; UpdateClient/1.0)');
114 
115  $response = curl_exec($ch);
116  $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
117 
118  curl_close($ch);
119 
120  if ($httpCode >= 200 && $httpCode < 300) {
121  return true;
122  } else {
123  return false;
124  }
125  }
126 
127  /**
128  * @brief get update settings from local update folder (system/update/update.ini) and return array|false
129  * @details will be called by xhr request from admin/js/update-generateLocalFilebase.php
130  * @return array|false
131  */
132  public function getUpdateSettings(): array|false
133  {
134  // read update.ini from local update folder
135  $this->updateSettings = parse_ini_file($this->localUpdateSystemPath.$this->updateFile);
136  if (count($this->updateSettings) < 1)
137  { // unable to read update.ini from local update folder
138  echo "Error: Unable to read update.ini from local update folder. Check if this file exists: ".$this->localUpdateSystemPath.$this->updateFile;
139  }
140  else {
141  // update.ini read successfully
142  // set update version
143  $this->updateVersion = $this->updateSettings['version'];
144 
145  // return update settings array
146  return $this->updateSettings;
147  }
148  // return false if something went wrong
149  return false;
150  }
151 
152 
153  /**
154  * @brief record migration in database
155  * @details will be called from runMigrations() if migration was successful
156  * @param $db
157  * @param $successfulMigrations
158  * @return void
159  */
160  public function recordMigration($db, $successfulMigrations): void
161  {
162  $output = '';
163  // check if migrations array is set and got at least 1 entry
164  if (is_array($successfulMigrations) && (count($successfulMigrations) > 0))
165  { // loop through all migrations
166  foreach ($successfulMigrations as $migrationVersion) {
167  // record migration
168  if (!$db->query("INSERT INTO {migrations} (`version`, `executed_at`) VALUES ('$migrationVersion', NOW())"))
169  { // error recording migration
170  sys::setSyslog($db, 53, 0, "Error recording migration for version $migrationVersion: " . $db->error . " ", 0, 0, 0, 0);
171  $output .= "Error recording migration for version $migrationVersion: " . $db->error . "<br>";
172  }
173  else
174  { // migration recorded successfully
175  sys::setSyslog($db, 53, 0, "Migration record for version $migrationVersion successful.", 0, 0, 0, 0);
176  $output .= "Migration for version $migrationVersion executed successfully.<br>";
177  }
178  }
179  }
180  else
181  { // no migrations to record
182  sys::setSyslog($db, 54, 0, "No Migrations to record. Is there a logical error?", 0, 0, 0, 0);
183  $output .= "No migrations to record.<br>";
184  }
185  echo $output;
186  }
187 
188  /**
189  * @brief Run the migration SQL files
190  * @details If update.ini contains migration files between the current version and the update version, this function will be called
191  * @param $db object the database object
192  * @param $lang array the language array
193  * @return void true|false if migrations were successful or not
194  */
195  function runMigrations(object $db, array $lang): void
196  {
197  require_once 'sys.php';
198  /** @param $db db */
199  ini_set('display_errors', 1);
200  error_reporting(E_ALL);
201 
202  // init variables
203  $output = '';
204  $totalMigration = 0;
205  $successfulMigrations = 0;
206  $failedMigrations = 0;
207  $successfulMigrationVersions = array();
208 
209  // log start of migration
210  sys::setSyslog($db, 56, 0, "runMigrations initialized. Updating from $this->currentVersion to $this->updateVersion", 0, 0, 0, 0);
211 
212  // run migrations
213  try
214  { // Start transaction, so we can roll back if something goes wrong
215  $db->beginTransaction();
216 
217  // Determine which migrations need to be executed
218  $migrationUrlBase = $this->updateServer . 'migration-';
219  $currentVersionParts = explode('.', $this->currentVersion);
220  $updateVersionParts = explode('.', $this->updateVersion);
221  // Start at the next minor version after the current version
222  $startIndex = (int)$currentVersionParts[2] + 1;
223  // End at the minor version of the update version
224  $endIndex = (int)$updateVersionParts[2];
225  $output .= '<br><br><div class="panel-group animated fadeIn slow delay-2s" id="accordion" role="tablist" aria-multiselectable="true">
226  <div class="panel panel-default">
227  <div class="panel-heading" role="tab" id="headingMigration">
228  <h4 class="panel-title">
229  <a class="collapsed" role="button" data-toggle="collapse" data-parent="#accordion" href="#collapseMigration" aria-expanded="true" aria-controls="collapseMigration">
230  '.$lang['UPDATE_MIGRATIONS'].'<br>
231  <small>(database updates)</small>
232  </a>
233  </h4>
234  </div>
235  <div id="collapseMigration" class="panel-collapse collapse" role="tabpanel" aria-labelledby="headingMigration">
236  <div class="panel-body">';
237 
238  // Loop through the migration files
239  for ($i = $startIndex; $i <= $endIndex; $i++)
240  {
241  $totalMigration++;
242  // build migration version
243  $migrationVersion = $currentVersionParts[0] . '.' . $currentVersionParts[1] . '.' . $i;
244  // build migration filename
245  $migrationUrl = $migrationUrlBase . $migrationVersion . '.sql';
246 
247  // check if migration was already executed
248  $migrationRecord = $db->query("SELECT version FROM {migrations} WHERE version = '$migrationVersion'")->fetch_assoc();
249 
250  // check if migration was already executed
251  if ($migrationRecord !== null)
252  { // migration was already executed
253  $output .= "$migrationVersion : ".$lang['UPDATE_MIGRATION_ALREADY_EXEC']."<br>";
254  sys::setSyslog($db, 53, 1, "Migration for build $migrationVersion was already executed.", 0, 0, 0, 0);
255  $failedMigrations++;
256  continue;
257  }
258  else {
259  // migration was not executed yet
260  // $output .= "Migration for build $migrationVersion was not executed yet.<br>";
261  // Fetch the migration file
262  $migrationSql = @file_get_contents($migrationUrl);
263  }
264 
265  // Check if the migration file was fetched successfully
266  if ($migrationSql === false)
267  { // Unable to fetch migration file
268  $output .= $lang["UPDATE_NO_MIGRATION_REQUIRED_FOR_BUILD"]." ".$migrationVersion."<br>";
269  sys::setSyslog($db, 53, 0, "No migration required for build $migrationVersion", 0, 0, 0, 0);
270  $failedMigrations++;
271  continue;
272  }
273  else
274  { // Fetched migration file successfully
275  $output .= "<span style=\"text-success\"><b>".$lang['UPDATE_FETCHED_MIGRATION_FILE_FROM']."</b>".$migrationUrl."</span><br>";
276  sys::setSyslog($db, 54, 0, "<b>Fetched migration file</b> from $migrationUrl", 0, 0, 0, 0);
277  $successfulMigrations++;
278  $successfulMigrationVersions[] = $migrationVersion;
279  }
280 
281  $output .= "Multi-query SQL: " . $migrationSql . "<br>";
282 
283  // explode the migration file into individual queries (array)
284  $sqlStatements = explode(';', $migrationSql);
285  // to keep track of the statement number
286  $statementCounter = 0;
287  // loop through all sql queries of current migration file
288  foreach ($sqlStatements as $sqlStatement)
289  { // increment statement counter
290  $statementCounter++;
291  // trim the sql statement
292  $sqlStatement = trim($sqlStatement);
293  // check if sql statement is not empty
294  if (!empty($sqlStatement))
295  { // execute the sql statement
296  if (!$db->query($sqlStatement))
297  { // migration failed
298  $output .= $migrationVersion." ".$lang['UPDATE_ERROR_EXEC_FAILED']." ".$statementCounter . $db->error . "<br>";
299  sys::setSyslog($db, 56, 2, "Error executing migration statement #$statementCounter for version (query failed) $migrationVersion: " . $db->error . " ", 0, 0, 0, 0);
300  $db->rollback(); // Rollback the transaction
301  return;
302  }
303  else
304  { // migration successful
305  $output .= "<span style=\"text-success\">$migrationVersion ".$lang['UPDATE_ERROR_EXEC_SUCCESS']." $statementCounter</span><br>";
306  sys::setSyslog($db, 53, 0, "executed migration statement #$statementCounter : $migrationUrlBase$migrationVersion.sql ", 0, 0, 0, 0);
307  }
308  }
309  }
310  } // end loop through migration files
311 
312  // Commit the transaction if all migrations executed successfully
313  $db->commit();
314 
315  // if there was at least one successful migration
316  if ($successfulMigrations > 0)
317  { // store migration version in database
318  $this->recordMigration($db, $successfulMigrationVersions);
319  foreach ($successfulMigrationVersions as $successfulMigrationVersion)
320  { // output successful migration version
321  $output .= "<span style=\"text-success\">$successfulMigrationVersion ".$lang['UPDATE_MIGRATION_RECORDED']."</span><br>";
322  }
323  // log successful migration
324  sys::setSyslog($db, 54, 0, "<b>Migration complete</b>Migrations executed successfully.", 0, 0, 0, 0);
325  $this->migrationSuccessful = true;
326  }
327  // END migrations executed successfully
328  }
329  // TRANSACTION FAILED - ROLLBACK
330  catch (\Exception $e)
331  { // An exception was thrown, rollback the transaction
332  if ($db->rollback() === true)
333  { // rollback successful
334  sys::setSyslog($db, 56, 2, "<b>Rolled back transaction</b> because there was an error executing migrations: " . $e->getMessage() ." ", 0, 0, 0, 0);
335  $output .= $lang['UPDATE_MIGRATION_ROLLED_BACK'] . $e->getMessage() . "\n";
336  }
337  else
338  { // rollback failed
339  sys::setSyslog($db, 56, 2, "<b>Rollback FAILED!</b>, additionally there was an error during migrations: " . $e->getMessage() ." ", 0, 0, 0, 0);
340  $output .= $lang['UPDATE_MIGRATION_ROLLBACK_FAILED'] . $e->getMessage() . "\n";
341  }
342  $this->migrationSuccessful = false;
343  }
344  // close migration panel body+panel
345  $output .= "</div></div></div>";
346 
347  // output ajax response
348  if (!empty($output))
349  { // will be returned to ajax request
350  echo $output;
351  }
352  else
353  { // no output was generated - this should not happen
354  sys::setSyslog($db, 56, 2, "No migrations were executed. Output is empty. output was not filled with any value during runMigrations(). (this is not possible?!)", 0, 0, 0, 0);
355  echo $lang['UPDATE_NO_MIGRATION_EXECUTED'];
356  }
357  }
358 
359  /**
360  * @brief read system/update/updateFiles.ini and fetch files from remote (GitHub) server
361  * @details will be called by xhr request from admin/js/update-fetchFiles.php
362  * @param $db object database connection
363  * @param $updateVersion string update version
364  * @param $lang array language array
365  */
366  public function fetchFiles(object $db, string $currentVersion, string $updateVersion, array $lang): void
367  {
368  // init updateSucceed flag, will be set to true if update was successful
369  $updateSucceed = false;
370 
371  // set update version
372  $this->currentVersion = $currentVersion;
373  $this->updateVersion = $updateVersion;
374 
375  // override $this->updateServer with GitHub url
376  $this->updateServer = $this->githubServer;
377 
378  // file fetched successfully
379  $basedir = __DIR__;
380  // remove last 15 chars from $basedir (system/update/)
381  $basedir = substr($basedir, 0, -14);
382  // $basedir = substr($basedir, 0, -8);
383 
384  $response = ''; // init output string, result of the update process, will be returned to the frontend
385  // check if updateFiles.ini exists
386  if (file_exists($basedir.$this->localUpdateSystemPath . $this->updateFilesFile))
387  {
388  // updateFiles.ini exists
389  // parse updateFiles.ini into array
390  $this->updateFiles = parse_ini_file($basedir.$this->localUpdateSystemPath . $this->updateFilesFile);
391  if (count($this->updateFiles) < 1)
392  { // unable to read updateFiles.ini from local update folder
393  $response .= "<span class=\"text-warning\"><p><i class=\"fa fa-exclamation-triangle text-warning\"></i>".$lang['UPDATE_FAST_FORWARD_INFO']."
394  <a href=\"#fastForwardUpdate\" id=\"fastForwardUpdateBtn\" class=\"btn btn-warning\">".$lang['UPDATE_FAST_FORWARD_BTN']." &nbsp;<i class=\"fa fa-fast-forward\"></i></a></span><br>";
395  }
396  else
397  { // count elements of updateFiles array
398  $totalUpdateFiles = count($this->updateFiles);
399  $failedFiles = 0;
400  $successFiles = 0;
401  $fetchFailed = 0;
402  $fetchSucceed = 0;
403  $processedFiles = 0;
404 
405  // updateFiles.ini read successfully
406  // fetch files from remote server
407  foreach ($this->updateFiles as $key => $value)
408  {
409  $processedFiles++;
410  // debug line can be removed $response .= "VALUE: $value <br>";
411 
412  // build file url
413  $fileUrl = $this->updateServer . $value;
414  // fetch next file to update
415  $file = file_get_contents($fileUrl);
416  if ($file === false)
417  {
418  // unable to fetch file
419  $response .= $lang['UPDATE_FETCH_FILE_FAILED'].$fileUrl."<br>";
420  $fetchFailed++;
421  }
422  else
423  { // count successful fetches
424  $fetchSucceed++;
425 
426  // Write file to local system
427  if (!file_put_contents($basedir.$value, $file))
428  { // unable to write file to local system
429  $failedFiles++; // count failed files
430  $response .= "<b> class=\"text-danger\">".$lang['UPDATE_UNABLE_TO_WRITE_FILE']."</b> " .$basedir.$value . "<br>";
431  }
432  else
433  { // file written successfully
434  $successFiles++; // count successful written files
435  $response .= "<b class=\"text-success animated fadeIn slow\">".$lang['UPDATE_FILE_WRITTEN']."</b> " . $basedir.$value . "<br>";
436  }
437  }
438  }
439  // check if all files were fetched successfully
440  if ($fetchFailed > 0)
441  { // at least one file could not be fetched
442  $response .= "<b class=\"text-danger\">".$lang['UNABLE_TO_FETCH_FILES_FROM_REMOTE_SERVER']." $fetchFailed</b><br>";
443  }
444  else if ($fetchSucceed === $totalUpdateFiles)
445  { // all files fetched successfully
446  $response .= "<b class=\"text-success\">$fetchSucceed ".$lang['UPDATE_SUCCESSFULLY_FETCHED']."</b><br>";
447  }
448  // check if all files were written successfully
449  if ($failedFiles > 0)
450  { // at least one file could not be written
451  $response .= "<b class=\"text-danger\">$failedFiles ".$lang['UPDATE_FAILED_FILES']."</b><br>";
452  }
453  else if ($successFiles === $totalUpdateFiles)
454  { // all files written successfully
455  $response .= "<b class=\"text-success\">".$lang['ALL']." ".$successFiles." ".$lang['UPDATE_ALL_FILES_SUCCESS']."</b><br>";
456  }
457  // check if all files were processed
458  if ($processedFiles === $totalUpdateFiles)
459  { // all files processed
460  $response .= "<b class=\"text-success\">".$lang['ALL']." ".$processedFiles." ".$lang['UPDATE_FILES_PROCESSED']."</b><br>";
461  }
462  else
463  { // not all files processed
464  $response .= "<b class=\"text-danger\">".$lang['UPDATE_NOT_ALL_FILES_PROCESSED']." ".$processedFiles." ".$lang['OF']." ".$totalUpdateFiles." ".$lang['UPDATE_FILES_PROCESSED'].".</b><br>";
465  }
466  // check if update was successful
467  if ($successFiles === $totalUpdateFiles)
468  { // update was successful
469  $updateSucceed = true;
470  }
471  else
472  { // update failed
473  $response .= "<h3 class=\"text-danger\">".$lang['UPDATE_FAILED']."</h3>";
474  }
475  }
476  }
477  else
478  { // updateFiles.ini does not exist
479  $response .= "<span class=\"text-danger\"><b>".$lang['ERROR'].":</b> ".$lang['UPDATE_FILES_INI_MISSING']." " .$basedir.$this->localUpdateSystemPath . $this->updateFilesFile . "</span>";
480  }
481 
482  // check if update was successful
483  if ($updateSucceed === true)
484  { // set new version in database
485  settings::setSetting($db, "yawkversion", $updateVersion, $lang);
486 
487  // get version from database to check if it was updated correctly
488  $version = settings::getSetting($db, "yawkversion");
489  if ($version == $updateVersion && $updateSucceed === true)
490  { // system version was updated successfully
491  $response .= "<h3 class=\"text-success\">".$lang['UPDATE_TO']." $updateVersion ".$lang['COMPLETED_SUCCESSFULLY']."</b><h3>";
492  sys::setSyslog($db, 54, 0, "<b>UPDATE COMPLETE</b> Migration and Files updated to $updateVersion.", 0, 0, 0, 0);
493  }
494  else
495  { // failed to update version in database
496  $response .= "<h3 class=\"text-danger\">".$lang['UPDATE_FAILED_TO_WRITE_VERSION_NUMBER']." ($updateVersion)</h3>";
497  }
498  }
499  else
500  { // update failed
501  $response .= "<h3 class=\"text-danger\">".$lang['UPDATE_FAILED_FROM']." $this->currentVersion ".$lang['TO']." $updateVersion.</h3>";
502  }
503  // return xhr response
504  echo $response;
505 
506  // close database connection
507  $db->close();
508  }
509 
510  /**
511  * @brief read update.ini from update server (https://update.yawk.io/update.ini) and return array|false
512  * @return false|array
513  */
514  public function readUpdateIniFromServer(): false|array
515  {
516  // URL of the remote INI file
517  $url = $this->updateServer.$this->updateFile;
518 
519  // Get the content of the remote INI file
520  $iniContent = file_get_contents($url);
521 
522  // Check if the content was retrieved successfully
523  if ($iniContent !== false)
524  { // Parse the INI content into an associative array
525  $config = parse_ini_string($iniContent);
526 
527  // Print the contents of the array
528  foreach ($config as $key => $value)
529  { // set update array
530  $updateConfig['UPDATE'][$key] = $value;
531  }
532  }
533  else
534  { // unable to read update.ini from remote server
535  echo "Error: Unable to read the remote INI file.";
536  }
537  return $updateConfig ?? false;
538  }
539 
540  /**
541  * @brief Read all files of current YaWK installation and write each file with path + MD5 hash to ini file
542  * to compare it later with the possible update files. This is used to verify the integrity of the files.
543  * @param $db object global db object
544  * @param $lang array global language array
545  * @return void
546  */
547  public function generateLocalFilebase(object $db, array $lang): void
548  {
549  $this->base_dir = dirname(__FILE__);
550  $this->base_dir = substr($this->base_dir, 0, -15); // remove last 15 chars
551  // echo "<p>Base directory: $this->base_dir</p>";
552 
553  // set path and filename of the ini file
554  $updatePath = '/system/update/';
555  $updateFolder = $this->base_dir.$updatePath;
556 
557  if (!is_dir($updateFolder)){
558  if (!mkdir($updateFolder, 0777, true) && !is_dir($updateFolder)) {
559  throw new \RuntimeException(sprintf('Unable to create "%s" - please check folder permissions or create folder by hand', $updateFolder));
560  }
561  }
562  $iniFileName = 'filebase.current.ini';
563  $input_folder = $this->base_dir;
564  $output_file = $updateFolder.$iniFileName;
565  $writeIniFile = true;
566 
567  // delete old file if exists
568  if (is_file($output_file)){
569  unlink($output_file);
570  }
571 
572  // Open the output file for writing
573  $output_handle = fopen($output_file, 'w');
574  if (!$output_handle) {
575  // handle the error (e.g. show an error message or log the error)
576  die("Failed to open file for writing: $output_file");
577  }
578 
579  // Get the full path of the input folder
580  $input_folder = realpath($input_folder);
581 
582  // Loop through all files in the input folder and its subfolders
583  echo "<p>".$lang['UPDATE_BUILDING_LOCAL_FILEBASE']." <i><small>$input_folder</small></i></p>";
584  $root_files = array();
585  $subfolder_files = array();
586  foreach(new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($input_folder)) as $file_path)
587  {
588  // Skip some excluded directories and hidden files
589  if ($file_path->isDir()
590  || $file_path->getFilename()[0] == '.'
591  || strpos($file_path, '/.git/') !== false
592  || strpos($file_path, '/.github/') !== false
593  || strpos($file_path, '/.idea/') !== false
594  || strpos($file_path, '/content/') !== false
595  || strpos($file_path, '/media/') !== false)
596  { // skip this file/folder
597  continue;
598  }
599 
600  // Calculate the MD5 hash of the file
601  $md5_hash = md5_file($file_path);
602 
603  // Get the full path of the file
604  $full_path = realpath($file_path);
605 
606  // Remove the input folder path from the full path to get the relative path
607  $relative_path = substr($full_path, strlen($input_folder) + 1);
608 
609  // Split the relative path into an array of path components
610  $path_components = explode('/', $relative_path);
611 
612  // Check if the path has only one component (i.e. it's a file in the root folder)
613  if (count($path_components) == 1) {
614  $root_files[$relative_path] = $md5_hash;
615  } else {
616  $subfolder_files[$relative_path] = $md5_hash;
617  }
618  }
619 
620  // Sort the arrays alphabetically by key (the relative path)
621  ksort($root_files);
622  ksort($subfolder_files);
623 
624  // Merge the two arrays so that the root files are listed first
625  $hashes = array_merge($root_files, $subfolder_files);
626 
627  // init counters
628  $totalFiles = count($hashes); // count total files
629  $totalFilesVerified = 0; // number of verified files
630  $totalFilesFailed = 0; // number of failed files
631 
632  // Write the sorted array to the output file in the .ini format
633  foreach ($hashes as $relative_path => $md5_hash)
634  { // write each file with its md5 hash to ini file
635  // echo "Writing $relative_path=$md5_hash to $output_file\n";
636  // Write the relative path and MD5 hash to the output file
637  if (file_put_contents($output_file, "$relative_path=$md5_hash\n", FILE_APPEND))
638  { // line written successfully
639  // echo '<p class="text-success">Verified '.$relative_path.' <small>'.$md5_hash.'</small></p>';
640  $totalFilesVerified++;
641  }
642  else
643  { // line failed
644  // echo '<p class="text-danger">Failed '.$relative_path.' <small>'.$md5_hash.'</small></p>';
645  $totalFilesFailed++;
646  }
647  }
648 
649  // Close the output file
650  echo '<p class="animated fadeIn slow delay-1s">'.$lang['UPDATE_INDEXING'].' <b>'.$totalFiles.'</b> '.$lang['FILES'].'. '.$lang['VERIFIED'].' <b>'.$totalFilesVerified.'</b> '.$lang['FILES'].'. '.$lang['FAILED'].' <b>'.$totalFilesFailed.'</b> '.$lang['FILES'].'.</p>';
651 
652  if (!is_file($output_file))
653  { // unable to write ini file
654  $iniFileWritten = "<h4 class=\"text-danger\">".$iniFileName." ".$lang['COULD_NOT_BE_WRITTEN']."</h4>";
655  }
656  else
657  { // ini file written successfully
658  $iniFileWritten = "<p class=\"animated fadeIn slow delay-5s\">".$lang['UPDATE_HASH_VALUES_WRITTEN_TO']." <b><a href=\"$updatePath$iniFileName\" target=\"_blank\">$updatePath$iniFileName</a></b></p>";
659  }
660 
661  // check if all files were verified
662  if ($totalFiles == $totalFilesVerified)
663  { // set success icon, message and colors
664  $icon = '<i class="fa fa-check-circle-o fa-2x text-success"></i>';
665  $done = "<h4 class=\"text-success animated fadeIn slow delay-4s\">".$totalFiles."&nbsp;".$lang['UPDATE_VERIFICATION_SUCCESS']."</h4>";
666  $iconFalse = '';
667  $successColor = ' text-success';
668  $failedColor = '';
669  }
670  else
671  { // set failure icon, message and colors
672  $iconFalse = '<i class="fa fa-exclamation-triangle fa-2x text-danger"></i>';
673  $done = "<h4 class=\"text-danger\"><b>".$totalFilesFailed." ".$lang['UPDATE_VERIFICATION_FAILED']."</b></h4>";
674  $failedColor = ' text-danger';
675  $successColor = '';
676  }
677 
678  echo "
679  <p class=\"animated fadeIn slow delay-2s$successColor\">$icon &nbsp;".$lang['UPDATE_GENERATED_HASH_VALUES']." <b>$totalFilesVerified / $totalFiles</b></p>";
680  echo"<h4 class=\"animated fadeIn slow delay-4s\">$done</h4>
681  $iniFileWritten";
682 
683  // check if any files failed
684  if ($totalFilesFailed > 0)
685  { // show amount of failed files
686  echo"<p class=\"animated fadeIn slow delay-3s$failedColor\">$iconFalse &nbsp;".$lang['UPDATE_FAILED_TO_VERIFY']." <b>$totalFilesFailed</b></p>";
687  }
688 
689  // Close the output file
690  if (!fclose($output_handle))
691  { // unable to close ini file
692  echo '<p class="animated fadeIn slow delay-5s">failed to close: '.$updateFolder.$iniFileName.'</p>';
693  }
694  }
695 
696  /**
697  * @brief read filebase.ini from update server (https://update.yawk.io/filebase.ini) and return array|false
698  * @details will be called by xhr request from admin/js/update-readUpdateFilebase.php
699  * The filebase.ini contains a list of all files with their md5 hash.
700  * This function returns the filebase as array. (to compare it later with the local filebase)
701  * @return array|false
702  */
703  public function readUpdateFilebaseFromServer(): array|false
704  {
705  // URL of the remote INI file
706  $url = $this->updateServer.'filebase.ini';
707 
708  // Get the content of the remote INI file
709  $iniContent = file_get_contents($url);
710 
711  // Check if the content was retrieved successfully
712  if ($iniContent !== false)
713  { // Parse the INI content into an associative array
714  $filebase = parse_ini_string($iniContent);
715 
716  // Print the contents of the array
717  foreach ($filebase as $key => $value)
718  { // set update array
719  $updateFilebase[$key] = $value;
720  }
721  }
722  else
723  { // unable to read filebase from remote server
724  echo "Error: Unable to read filebase from remote server. Check if this file exists: $url";
725  }
726  // return array or false
727  return $updateFilebase ?? false;
728  }
729 
730  } // EOF class update
731 } // EOF namespace
print $lang['FILEMAN_UPLOAD']
die
Definition: block-user.php:27
Mysqli database class; returns db connection object.
Definition: db.php:16
static setSetting($db, $property, $value, $lang)
Set value for property into settings database.
Definition: settings.php:502
static getSetting($db, $property)
Get and return value for property from settings database.
Definition: settings.php:470
The sys class - handles yawk's system core functions.
Definition: sys.php:17
The update class - handles yawk's system update functions.
Definition: update.php:21
string $localUpdateSystemPath
Definition: update.php:47
array $updateFiles
Definition: update.php:67
recordMigration($db, $successfulMigrations)
record migration in database
Definition: update.php:159
string $base_dir
Definition: update.php:23
bool $migrationSuccessful
Definition: update.php:72
string $updateServer
Definition: update.php:26
string $updateFilesFile
Definition: update.php:62
__construct()
update constructor. Check if allow_url_fopen is enabled
Definition: update.php:78
string $currentVersion
Definition: update.php:37
string $updateVersion
Definition: update.php:42
string $updateFilebase
Definition: update.php:52
isServerReachable(string $url)
check if update server is reachable
Definition: update.php:103
runMigrations(object $db, array $lang)
Run the migration SQL files.
Definition: update.php:194
string $githubServer
Definition: update.php:29
getUpdateSettings()
get update settings from local update folder (system/update/update.ini) and return array|false
Definition: update.php:131
fetchFiles(object $db, string $currentVersion, string $updateVersion, array $lang)
read system/update/updateFiles.ini and fetch files from remote (GitHub) server
Definition: update.php:365
readUpdateFilebaseFromServer()
read filebase.ini from update server (https://update.yawk.io/filebase.ini) and return array|false
Definition: update.php:702
string $updateFile
Definition: update.php:32
generateLocalFilebase(object $db, array $lang)
Read all files of current YaWK installation and write each file with path + MD5 hash to ini file to c...
Definition: update.php:546
readUpdateIniFromServer()
read update.ini from update server (https://update.yawk.io/update.ini) and return array|false
Definition: update.php:513
array $updateSettings
Definition: update.php:57
This class serves methods to create backup from files.
Definition: AdminLTE.php:2
foreach($updateFilebase as $key=> $value) foreach($localFilebase as $filePath=> $localHash) $response
$url
Definition: user-new.php:101
$value
$i