<?php
/**
 * @copyright (c) Kufer Software GmbH, Oderstraße 7, D-84453 Mühldorf
 */


class LibPreview
{
  
  public static function copyFilesTo($pFrom, $pTo)
  {
    
    if (StringOperations::strpos($pFrom, ConstantOperations::getPathForCoreSettings()) !== false) {
      copy($pFrom . FILENAME_SYSTEMSETTINGS, $pTo . FILENAME_SYSTEMSETTINGS);

    
    } elseif (StringOperations::strpos($pFrom, ConstantOperations::getPathForKuferToolsUserOrig()) !== false) {
      FSOperations::copyDir($pFrom, $pTo);
    } elseif (file_exists($pFrom) && file_exists($pTo)) {
      $d = dir($pFrom);
      while (false !== ($fn = $d->read())) {
        if ($fn != '.' && $fn != '..' && !is_dir($pFrom . $fn)) {
          copy($pFrom . $fn, $pTo . $fn);
        }
      }
      $d->close();
    }
  }

  
  private function getConfigDirs()
  {
    $dirs = array();
    $dirs[] = ConstantOperations::getPathForCoreSettings();
    $dirs[] = ConstantOperations::getPathForSettingsOrig();
    $dirs[] = ConstantOperations::getPathForSightsOrig();
    $dirs[] = ConstantOperations::getPathForKuferToolsUserOrig();

    return array_merge($dirs, ConstantOperations::getPathForSightsModus());
  }

  
  public function createPreviewDirs()
  {
    $libInfo = new LibInformation();
    $dirs = $this->getConfigDirs();
    foreach ($dirs as $dir) {
      $dir_prev = $dir . 'preview' . DIRECTORY_SEPARATOR;
      if (!file_exists($dir_prev)) {
        if (@mkdir($dir_prev) == true) {
          if ($libInfo->isDirWriteable($dir_prev)) {
            $this->copyFilesTo($dir, $dir_prev);
          }
        } else {
          throw new Exception('Das Verzeichnis "' . $dir_prev . '" konnte nicht angelegt werden!');
        }
      }
    }
  }

  
  public function checkPreviewRequirement()
  {
    $libInfo = new LibInformation();
    $dirs = $this->getConfigDirs();
    foreach ($dirs as $dir) {
      if (!$libInfo->isDirWriteable($dir . 'preview' . DIRECTORY_SEPARATOR)) {
        return false;
      }
    }

    return SIGHTS_LOADFROMFILE && SETTINGS_LOADFROMFILE && LibInformation::checkDbConnection(1);
  }

  
  public function setPreviewMode($pActive)
  {
    
    if (!$this->checkPreviewRequirement()) {
      SessionOperations::setPreviewMode(false);
    }

    
    SessionOperations::setPreviewMode($pActive);
  }

  
  public function copyLiveToPreview()
  {
    $this->backupBeforeCopy('Sicherung wg. Kopie Live =&gt; Preview');
    $dirs = $this->getConfigDirs();
    foreach ($dirs as $dir) {
      $dir_prev = $dir . 'preview' . DIRECTORY_SEPARATOR;
      $this->copyFilesTo($dir, $dir_prev);
    }
  }

  
  public function copyPreviewToLive()
  {
    $this->backupBeforeCopy('Sicherung wg. Kopie Preview =&gt; Live');
    $dirs = $this->getConfigDirs();
    foreach ($dirs as $dir) {
      $dir_prev = $dir . 'preview' . DIRECTORY_SEPARATOR;
      $this->copyFilesTo($dir_prev, $dir);
    }
  }

  public function backupBeforeCopy($msg)
  {
    $pathToGlobalSettings = ConstantOperations::getPathForCoreSettings();
    $layoutpathwithDirSeparator = new Path(ConstantOperations::getPathForSettingsOrig());
    $layoutpath = $layoutpathwithDirSeparator->dirOnly();

    $sightspathwithDirSeparator = new Path(ConstantOperations::getPathForSightsOrig());
    $sightspath = $sightspathwithDirSeparator->dirOnly();

    $files = array(
      $pathToGlobalSettings . 'allgeinstell.cfg',
      $pathToGlobalSettings . 'connect.php',
      $pathToGlobalSettings . 'preview' . DIRECTORY_SEPARATOR,
      $layoutpath->toString(),
      $sightspath->toString()
    );

    $libBackup = new LibBackup();
    $libBackup->createMultipartBackup($msg, $files);
  }
}


class LibSightsFolder
{
  
  private $sfc;

  
  private $modus;

  public function __construct($pModus)
  {
    $this->modus = $pModus;

    $this->sfc = new SightsFolderControll();
    $this->sfc->loadSightsFolderFromSettings();
  }

  
  public function createDirs($pDir, $pSettingsFolder = false)
  {
    $libInfo = new LibInformation();
    if ($pSettingsFolder) {
      $dirSight = ConstantOperations::getPathForSettingsOrig();
    } else {
      $dirSight = ConstantOperations::getPathForSightsOrig();
    }
    if (!file_exists($pDir)) {
      if (@mkdir($pDir) == true) {
        if ($libInfo->isDirWriteable($pDir)) {
          
          LibPreview::copyFilesTo($dirSight, $pDir);
        }
      } else {
        throw new Exception('Das Verzeichnis "' . $pDir . '" konnte nicht angelegt werden!');
      }
    }
  }

  
  public function checkModeRequirement()
  {
    $libInfo = new LibInformation();
    $dirSight = ConstantOperations::getPathForSightsOrig();
    $dirSetting = ConstantOperations::getPathForSettingsOrig();


    $folder = $this->sfc->getFolderByMode($this->modus);
    if ($folder && trim($folder->foldername) != '') {
      $dirModeSight = $dirSight . $folder->foldername . '/';
      $dirModeSetting = $dirSetting . $folder->foldername . '/';

      if (!file_exists($dirModeSight)) {
        $this->createDirs($dirModeSight);
      }
      if (!$libInfo->isDirWriteable($dirModeSight)) {
        return false;
      }

      if (!file_exists($dirModeSetting)) {
        $this->createDirs($dirModeSetting, true);
      }
      if (!$libInfo->isDirWriteable($dirModeSetting)) {
        return false;
      }
    } else {
      return false;
    }

    return SIGHTS_LOADFROMFILE && SETTINGS_LOADFROMFILE && LibInformation::checkDbConnection(1);
  }

  
  public function setAdminMode($pActive = true)
  {
    if (!$pActive || !$this->checkModeRequirement()) {
      SessionOperations::setAdminMode(KUFERWEB_MODUS_NORMAL);
    } else {
      SessionOperations::setAdminMode($this->modus);
    }
  }
}


class LibUpdate extends LibConfigurable
{
  public function __construct()
  {
    $this->configDir = ADMINBASE . 'classes/settings/config/update/';
  }

  
  private function versionCompare($pV1, $pV2)
  {
    
    
    $v1 = explode('.', $pV1);
    $v2 = explode('.', $pV2);

    
    
    while (count($v1) != count($v2)) {
      if (count($v1) > count($v2)) {
        array_push($v2, 0);
      } else {
        array_push($v1, 0);
      }
    }

    
    for ($i = 0, $c = count($v1); $i < $c; $i++) {
      if ($v1[$i] > $v2[$i]) {
        return 2;
      } elseif ($v1[$i] < $v2[$i]) {
        return 0;
      }
    }

    return 1;
  }

  
  public function checkBrVersion($pVersion)
  {
    $lib = new LibInformation();
    $version = $lib->getBrVersion();

    return $this->versionCompare($version, $pVersion->reqBrVersion) > 0;
  }

  
  public function checkBasysStructure($pVersion)
  {
    $lib = new LibInformation();
    $version = $lib->getBasysStructure();
    $reqVersion = $pVersion->reqBasysSqlStructure;

    return $this->versionCompare($version, $reqVersion) > 0;
  }

  
  public function checkPhpVersion($pVersion)
  {
    $lib = new LibInformation();
    $version = $lib->getPhpVersion();

    return version_compare($version, $pVersion->reqPhpVersion, '>=');
  }

  
  public function checkMaxPhpVersion($pVersion)
  {
    $lib = new LibInformation();
    $version = $lib->getPhpVersion();

    return version_compare($version, $pVersion->maxPhpVersion, '<=');
  }

  
  public function checkMysqlVersion($pVersion)
  {
    $lib = new LibInformation();
    if ($lib->isMariaDB()) {
      return true;
    } else {
      $version = $lib->getMysqlVersion();

      return $this->versionCompare($version, $pVersion->reqMysqlVersion) > 0;
    }
  }

  
  public function checkMariaDBVersion($pVersion)
  {
    $lib = new LibInformation();
    if ($lib->isMariaDB()) {
      $version = $lib->getMariaDBVersion();

      return $this->versionCompare($version, $pVersion->reqMariaDbVersion) > 0;
    } else {
      return true;
    }
  }

  
  public function checkRequirements($pVersion)
  {
    $libInfo = new LibInformation();
    $bArt = $libInfo->getBetriebsart();

    $req = array();
    if ($pVersion->reqBrVersion != '' && $bArt != BA_DIREKT) {
      $req[] = array(
        'label' => 'Bas_Replikat',
        'value' => $pVersion->reqBrVersion,
        'check' => $this->checkBrVersion($pVersion)
      );
    }

    if ($pVersion->reqPhpVersion != '') {
      $req[] = array(
        'label' => 'PHP',
        'value' => $pVersion->reqPhpVersion,
        'check' => $this->checkPhpVersion($pVersion)
      );
    }

    if ($pVersion->maxPhpVersion != '') {
      $req[] = array(
        'label' => 'Maximale PHP-Version',
        'value' => $pVersion->maxPhpVersion,
        'check' => $this->checkMaxPhpVersion($pVersion)
      );
    }

    if ($pVersion->reqMysqlVersion != '' && $bArt != BA_DIREKT) {
      $req[] = array(
        'label' => 'MySQL',
        'value' => $pVersion->reqMysqlVersion,
        'check' => $this->checkMysqlVersion($pVersion)
      );
    }

    if ($pVersion->reqMariaDbVersion != '' && $bArt != BA_DIREKT) {
      $req[] = array(
        'label' => 'MariaDB',
        'value' => $pVersion->reqMariaDbVersion,
        'check' => $this->checkMariaDBVersion($pVersion)
      );
    }

    $basysStructure = $pVersion->reqBasysSqlStructure;
    if ($basysStructure != '') {
      $req[] = array(
        'label' => 'BASys Datenbankstruktur',
        'value' => $basysStructure,
        'check' => $this->checkBasysStructure($pVersion)
      );
    }

    return $req;
  }
}

class LibBackup
{
  private $backupPath;

  public function __construct()
  {
    $this->backupPath = ADMINBASE . 'backups/';
  }

  
  private function copyDir($pCopyDir, $pDestDir, $pExcludeDirs = array())
  {
    FSOperations::copyDir($pCopyDir, $pDestDir, $pExcludeDirs);
  }

  
  private function mkDir($pDir)
  {
    return FSOperations::mkDir($pDir);
  }

  
  private function removeDir($pDir)
  {
    FSOperations::removeDir($pDir);
  }

  
  private function objectToFile($pObject, $pFileName)
  {
    $content = "<?php \n";
    $content .= '$object = new ' . get_class($pObject) . "();\n";
    $objVars = get_object_vars($pObject);
    foreach ($objVars as $varname => $varvalue) {
      $content .= '$object->' . $varname . ' = \'' . $varvalue . "';\n";
    }
    $content .= ' ?>';

    $f = @fopen($pFileName, 'w');
    if ($f === false) {
      throw new Exception('Das öffnen der Datei "' . $pFileName . '" ist fehlgeschlagen!');
    }
    fwrite($f, $content);
    fclose($f);
  }

  
  private function objectFromFile($pFileName)
  {
    include $pFileName;
    if (isset($object)) {
      return $object;
    } else {
      return false;
    }
  }

  
  private function randBackupFolder()
  {
    $i = 0;
    do {
      $result = date('YmdHis');
      if ($i != 0) {
        $result .= '_' . $i;
      }
      $i++;
    } while (file_exists($this->backupPath . $result));

    return $result;
  }

  
  private function calcBasePath($pFileList)
  {
    $basePath = '';
    foreach ($pFileList as $file) {
      $dir = $file;
      if (!is_dir($file)) {
        $dir = dirname($file);
      }
      $dir = str_replace('\\', '/', $dir);

      if ($basePath == '') {
        $basePath = $dir;
      }

      $baseAry = explode('/', $basePath);
      $dirAry = explode('/', $dir);
      $base = '';
      if (StringOperations::strtoupper(StringOperations::substr(php_uname('s'), 0, 3)) != 'WIN') {
        $base = '/';
      }
      foreach ($baseAry as $i => $folder) {
        if (isset($dirAry[$i]) && $folder == $dirAry[$i]) {
          if ($folder != '') { 
            $base .= $folder . '/';
          }
        } else {
          break;
        }
      }

      $basePath = $base;
    }

    if (trim($basePath) == '') {
      throw new Exception('Es konnte kein Basispfad berechnet werden!');
    }

    return $basePath;
  }

  
  public function createBackup($pTitle, $pTarget)
  {
    return $this->createMultipartBackup($pTitle, array($pTarget));
  }

  
  public function createMultipartBackup($pTitle, $pFiles)
  {
    if (empty($pFiles)) {
      return 'Es wurden keine zu sichernden ' . $pTitle . ' gefunden.';
    }

    foreach ($pFiles as $key => $file) {
      $pFiles[$key] = str_replace('\\', '/', $file);
    }

    
    $bFolderName = $this->randBackupFolder();
    $bFullPath = StringOperations::setLastChar($this->backupPath . $bFolderName, '/');

    $basePath = $this->calcBasePath($pFiles);

    
    $backup = new Backup();
    $backup->id = $bFolderName;
    $backup->title = $pTitle;
    $backup->date = time();
    $backup->originalPath = $basePath;
    $backup->backupPath = $bFullPath;
    $backup->backupFile = $this->backupPath . $bFolderName . '.php';

    
    try {
      foreach ($pFiles as $file) {
        
        if (is_dir($file)) {
          $file = StringOperations::setLastChar($file, '/');
        }

        
        $backupTo = $bFullPath . str_replace($basePath, '', $file);

        
        if (is_dir($file)) {
          $this->copyDir($file, $backupTo, array($this->backupPath));
        } else {
          $dir = dirname($backupTo);
          if (!file_exists($dir)) {
            if (!$this->mkDir($dir)) {
              throw new Exception('Das Verzeichnis "' . $dir . '" konnte nicht erstellt werden!');
            }
          }
          if (!copy($file, $backupTo)) {
            throw new Exception('Die Datei "' . $backupTo . '" konnte nicht erstellt werden!');
          }
        }
      }

      
      $this->objectToFile($backup, $backup->backupFile);
    } catch (Exception $e) {
      return $e->getMessage();
    }
  }

  
  public function restoreBackup($pBackup)
  {
    try {
      $this->copyDir($pBackup->backupPath, $pBackup->originalPath);

      return true;
    } catch (Exception $e) {
      return $e->getMessage();
    }
  }

  
  public function removeBackup($pBackup)
  {
    try {
      $this->removeDir($pBackup->backupPath);
      unlink($pBackup->backupFile);

      return true;
    } catch (Exception $e) {
      return $e->getMessage();
    }
  }

  
  public function getBackups()
  {
    $backups = array();
    $entries = scandir($this->backupPath);
    foreach ($entries as $filename) {
      $wholePath = $this->backupPath . $filename;
      $charAt1 = StringOperations::substr($filename, 0, 1);
      if ($charAt1 != '.' && !is_dir($wholePath)) {
        $b = $this->objectFromFile($wholePath);
        if ($b !== false) {
          $backups[] = $b;
        }
      }
    }

    
    for ($i = 0; $i < count($backups); $i++) {
      for ($j = 0; $j < count($backups) - 1; $j++) {
        $obj1 = $backups[$j];
        $obj2 = $backups[$j + 1];
        if ($obj1->date < $obj2->date) {
          $backups[$j] = $obj2;
          $backups[$j + 1] = $obj1;
        }
      }
    }

    return $backups;
  }

  
  public function getBackupById($pId)
  {
    $backups = $this->getBackups();
    foreach ($backups as $b) {
      if ($b->id == $pId) {
        return $b;
      }
    }

    throw new Exception('Eine Sicherung mit der Id "' . $pId . '" existiert nicht!');
  }
}

interface IUpdateStep
{
  
  public function getVersion();

  
  public function getCaption();

  
  public function getDescription();

  
  public function isNecessary();

  
  public function doChanges();
}

class Backup
{
  
  public $id;
  
  public $title;
  
  public $date;
  
  public $originalPath;
  
  public $backupPath;
  
  public $backupFile;
}



class LibNews
{
  private $dbWorkNews;
  private $news;

  public function __construct()
  {
  }

  
  public function getLength()
  {
    
    $default = 10;

    
    $length = intval(GetOperations::getVarValue('anzahl', $default));

    
    return ($length >= 1) ? $length : $default;
  }

  
  public function getCurrentPage()
  {
    
    $default = 1;

    $maxPages = $this->getNumPages();

    
    $page = intval(GetOperations::getVarValue('seite', $default));

    
    return ($page >= 1 && $page <= $maxPages) ? $page : $default;
  }

  
  public function getNumNews()
  {
    if ($this->news === null) {
      $this->loadNews();
    }

    return count($this->news);
  }

  
  public function getNumPages()
  {
    $length = $this->getLength();
    $numNews = $this->getNumNews();

    
    $numPages = $numNews / $length;
    $intPages = intval($numPages);

    
    return ($intPages >= $numPages) ? $intPages : $intPages + 1;
  }

  
  public function loadNews()
  {
    if ($this->dbWorkNews === null) {
      $this->dbWorkNews = new DBWorkAktuellesWB();
    }

    
    $ary = $this->dbWorkNews->getNews();

    
    $this->news = array();
    foreach ($ary as $news) {
      $this->news[$news->id] = $news;
    }
  }

  
  public function getNews()
  {
    if ($this->news === null) {
      $this->loadNews();
    }

    
    $length = $this->getLength();
    $page = $this->getCurrentPage();

    
    $pointer = ($page - 1) * $length;

    return array_slice($this->news, $pointer, $length);
  }

  
  public function getSingleNews($pNewsId)
  {
    if ($this->dbWorkNews === null) {
      $this->dbWorkNews = new DBWorkAktuellesWB();
    }

    
    $news = $this->dbWorkNews->getNewsById($pNewsId);

    
    if (is_object($news) && $news->id !== null) {
      return $news;
    } else {
      return null;
    }
  }

  
  public function createNews($pTitle, $pContent, $pAutor, $pBildpfad, $pShowNews, $pShowRss, $pGueltigAb = DEFAULTDATE, $pGueltigBis = DEFAULTDATE)
  {
    if ($this->dbWorkNews === null) {
      $this->dbWorkNews = new DBWorkAktuellesWB();
    }

    
    if ($pTitle === '' || $pContent === '') {
      return false;
    }

    
    return $this->dbWorkNews->createNews($pTitle, $pContent, $pAutor, $pBildpfad, $pShowNews, $pShowRss, $pGueltigAb, $pGueltigBis);
  }

  
  public function checkNews($pNewsId, $pTitle, $pContent, $pAutor, $pBildpfad, $pShowNews, $pShowRss, $pGueltigAb, $pGueltigBis)
  {
    $showRss = ($pShowRss !== 'false' && $pShowRss) ? 1 : 0;
    $showNews = ($pShowNews !== 'false' && $pShowNews) ? 1 : 0;

    $news = $this->dbWorkNews->getNewsById($pNewsId);

    if (empty($news->gueltigBis)) {
      $news->gueltigBis = DEFAULTDATE;
    }
    if ($pGueltigBis !== null && empty($pGueltigBis)) {
      $pGueltigBis = DEFAULTDATE;
    }

    if (is_object($news)
       && ($pTitle === null || $pTitle === $news->titel)
       && ($pContent === null || $pContent === $news->inhalt)
       && ($pAutor === null || $pAutor === $news->autor)
       && ($pBildpfad === null || $pBildpfad === $news->bildpfad)
       && ($pShowNews === null || $showNews === intval($news->anzeige_news))
       && ($pShowRss === null || $showRss === intval($news->anzeige_rss))
       && ($pGueltigAb === null || $pGueltigAb === $news->gueltigAb)
       && ($pGueltigBis === null || $pGueltigBis === $news->gueltigBis)
    ) {
      return true;
    } else {
      return false;
    }
  }

  
  public function updateNews(
    $pNewsId,
    $pTitle = null,
    $pContent = null,
    $pAutor = null,
    $pBildpfad = null,
    $pShowNews = null,
    $pShowRss = null,
    $pGueltigab = null,
    $pGueltigbis = null,
    $pErstelldatum = null
  ) {
    if ($pTitle === null && $pContent === null && $pAutor === null && $pBildpfad === null && $pShowNews === null && $pShowRss === null && $pErstelldatum === null) {
      return false;
    }

    if ($this->dbWorkNews === null) {
      $this->dbWorkNews = new DBWorkAktuellesWB();
    }

    $this->dbWorkNews->updateNews($pNewsId, $pTitle, $pContent, $pAutor, $pBildpfad, $pShowNews, $pShowRss, $pGueltigab, $pGueltigbis, $pErstelldatum);

    return true;
  }

  
  public function deleteNews($pNewsId)
  {
    if ($this->dbWorkNews === null) {
      $this->dbWorkNews = new DBWorkAktuellesWB();
    }

    $this->dbWorkNews->deleteNews($pNewsId);
  }

  
  public function deleteNewsBulk($pNewsIds)
  {
    if ($this->dbWorkNews === null) {
      $this->dbWorkNews = new DBWorkAktuellesWB();
    }

    $this->dbWorkNews->deleteNewsBulk(explode(',', $pNewsIds));
  }

  
  public function deleteNewsOlderThan($pDatum)
  {
    if ($this->dbWorkNews === null) {
      $this->dbWorkNews = new DBWorkAktuellesWB();
    }

    $this->dbWorkNews->deleteNewsOlderThan($pDatum);
  }

  public function checkDeletion($pNewsIds)
  {
    if ($this->dbWorkNews === null) {
      $this->dbWorkNews = new DBWorkAktuellesWB();
    }

    return $this->dbWorkNews->getExistingNewsIds(explode(',', $pNewsIds));
  }
}


class LibWBFileUpload
{
  
  public function getDirectoryContent($pDirName)
  {
    return FSOperations::getDirContent($pDirName, true);
  }

  
  public function getFileCount($pDirName)
  {
    return count($this->getDirectoryContent($pDirName));
  }

  
  public function getOldFiles($pDirName, $pThreshold)
  {
    $pDirName = StringOperations::setLastChar($pDirName, DIRECTORY_SEPARATOR);
    $files = $this->getDirectoryContent($pDirName);
    $result = array();
    foreach ($files as $file) {
      if (FSOperations::getCorrectMTime($pDirName . $file) < $pThreshold) {
        $result[] = $file;
      }
    }

    return $result;
  }

  
  public function deleteOldFiles($pDirName, $pThreshold)
  {
    $pDirName = StringOperations::setLastChar($pDirName, DIRECTORY_SEPARATOR);
    $oldFiles = $this->getOldFiles($pDirName, $pThreshold);
    $deletedFilesCounter = 0;
    foreach ($oldFiles as $file) {
      if (FSOperations::delFile($pDirName . $file)) {
        $deletedFilesCounter++;
      }
    }

    return $deletedFilesCounter;
  }
}

class LibValidateFileHashes
{
  public const ignoreDirs = array(
      '.svn/',
      '.vscode/',
      '_defaultConfig/',
      'ablage/',
      'nbproject/',
      'log/',
      'admin/classes/smarty/data/',
      'kufertools/settings/user/',
      'webbasys/layout/',
      'webbasys/resources/user/',
      'webbasys/bilder/',
      'webbasys/tmp/',
      'tests/',
      '.php_cs.dist',
      '.php-cs-fixer.dist.php',
      'webtools/config/(users|connect|SprachSettingsObject).php',
      'classes/constants.php',
      'classes/userconstants.php'
  );

  protected static function getFileHashesPath(): string
  {
    return \FSOperations::path(BASEDIR, 'webtools', 'config', 'fileshashes.json.gz');
  }

  
  public static function generateHashes(): void
  {
    $ignoreDirsReal = '&^[\\/]?(' . str_replace('\\', '/', implode('|', array_map(\FunctionOperations::args(\FSOperations::class . '::path', BASEDIR), static::ignoreDirs))) . ')&i';
    $baseDirStrLen = \StringOperations::strlen(BASEDIR);
    $jsonObj = \FSOperations::listFilesRecursiveInDirectory(\FSOperations::path(BASEDIR))
      ->map(static function(string $file): string {
        return str_replace('\\', '/', $file);
      })
      ->filter(static function(string $file) use ($ignoreDirsReal) {
        return preg_match($ignoreDirsReal, $file) === 0 && preg_match('/\\.(php|js|sgt|wss|css|html)$/', $file) === 1;
      })
      ->map(static function(string $file) {
        return new \Kufer\Web\Classes\Addfunctions\Pair($file, static::hashFile($file));
      })
      ->map(static function(\Kufer\Web\Classes\Addfunctions\Pair $pair) use ($baseDirStrLen) {
        $pair->first = \StringOperations::substr($pair->first, $baseDirStrLen);

        return $pair;
      })
      ->toKeyValueArrayFromPairs();

    file_put_contents(
      static::getFileHashesPath(),
      gzencode(\JsonOperations::encode($jsonObj), 5)
    );
  }

  protected static function hashFile(string $file): string
  {
    
    return @\hash_file('crc32c', $file);
  }

  
  public static function validateHashes(): array
  {
    $errors = array();
    $fileContents = @file_get_contents(static::getFileHashesPath());
    if ($fileContents === false) {
      return array('Auf die Datei ' . static::getFileHashesPath() . ' konnte nicht zugegriffen werden.');
    }

    $jsonObj = json_decode(gzdecode($fileContents, 1024 * 1024 * 4));
    unset($fileContents);

    foreach (get_object_vars($jsonObj) as $file => $hash) {
      $realFile = \FSOperations::path(BASEDIR, $file);
      $newHash = static::hashFile($realFile);
      if ($newHash === false) {
        $errors[] = "Erwartete Datei {$file} existiert nicht!";
      } elseif ($newHash !== $hash) {
        $errors[] = "Hash von Datei {$file} ist fehlerhaft: {$hash} != {$newHash}";
      }
    }

    return $errors;
  }

  
  public static function renameKuferToolsDirectories(): array
  {
    $directories = \FSOperations::listFilesRecursiveInDirectory(\FSOperations::path(BASEDIR, 'kufertools'))
      ->filter(static function(string $file) {
        return \StringOperations::endsWith($file, '.php');
      })
      ->map(static function(string $file): string {
        return dirname($file);
      })
      ->filter(static function(string $file) {
        $startsWithDrive = preg_match('/^[A-Z]:/', $file) === 1;

        return preg_match_all('/[A-Z]/', $file) > ($startsWithDrive ? 1 : 0) && preg_match('/kufertools.*[A-Z]/', $file) === 1;
      })
      ->unique()
      ->sort(static function(string $a, string $b): int {
        return strlen($b) - strlen($a);
      });

    $dirs = array();
    $rename = array();
    foreach ($directories->toIterator() as $dir) {
      $dir = str_replace('\\', '/', $dir);
      $parts = explode('/', $dir);
      for ($i = count($parts) - 1; $i >= 1; --$i) {
        if ($parts[$i] === basename(BASEDIR) || $parts[$i] === 'kufertools' || $parts[$i] === '.') {
          break;
        }

        if (preg_match('/[A-Z]/', $parts[$i])) {
          $oldDir = implode(DIRECTORY_SEPARATOR, array_slice($parts, 0, $i + 1));
          foreach ($dirs as $old => $new) {
            if (\StringOperations::startsWith($oldDir, $old . DIRECTORY_SEPARATOR)) {
              $oldDir = $new . \StringOperations::substr($oldDir, \StringOperations::strlen($old));
            }
          }

          if (isset($dirs[$oldDir])) {
            continue;
          }

          $newDir = implode(DIRECTORY_SEPARATOR, array_slice(explode(DIRECTORY_SEPARATOR, $oldDir), 0, $i)) . DIRECTORY_SEPARATOR . \StringOperations::strtolower($parts[$i]);

          $dirs[$oldDir] = $newDir;
          $rename[] = array($oldDir, $newDir);
        }
      }
    }

    usort($rename, static function(array $a, array $b): int {
      return strlen($b[0]) - strlen($a[0]);
    });

    $result = array();
    $errors = array();
    foreach ($rename as $files) {
      list($oldDir, $newDir) = $files;
      if (@rename($oldDir, $newDir)) {
        $result[] = $oldDir . ' -> ' . $newDir;
      } else {
        $errors[] = $oldDir . ' -> ' . $newDir;
      }

      
    }

    return array($result, $errors);
  }
}