app = $app; try { $this->oBackupService = $this->app->Container->get('BackupService'); $this->oBackupGateway = $this->app->Container->get('BackupGateway'); } catch (RuntimeException $e) { $this->app->Tpl->Set('MESSAGE', '
Backup Fehler: ' . $e->getMessage() . '
'); } if($intern){ return; } $id = $this->app->Secure->GetGET('id'); if(is_numeric($id)){ $this->app->Tpl->Set('SUBHEADING', ": " . $this->app->DB->Select("SELECT nummer FROM artikel WHERE id=$id LIMIT 1")); } $this->app->ActionHandlerInit($this); $this->app->ActionHandler('list', 'BackupList'); $this->app->ActionHandler('create', 'BackupCreate'); $this->app->ActionHandler('recover', 'BackupRecover'); $this->app->ActionHandler('readstatus', 'ReadStatus'); $this->app->ActionHandler("downloadsnapshot", "BackupDownloadSnapshot"); $this->app->ActionHandler("delete", "BackupDelete"); $this->app->ActionHandler("importer", "BackupImporter"); $this->host = $this->app->Conf->WFdbhost; $this->database = $this->app->Conf->WFdbname; $this->user = $this->app->Conf->WFdbuser; $this->password = $this->app->Conf->WFdbpass; $this->pfad = (isset($this->app->Conf->WFbackup) && is_dir($this->app->Conf->WFbackup) ? rtrim($this->app->Conf->WFbackup, '/') . "/" : "../backup/snapshots/"); $this->app->ActionHandlerListen($app); $this->app->erp->Headlines('Backup'); } public function Install() { // ADD Cron $check = $this->app->DB->SelectRow("SELECT `id` FROM `prozessstarter` WHERE `parameter` = 'backup' LIMIT 1"); $dateTime = new DateTime('tomorrow'); $startTime = sprintf('%s %s', $dateTime->format('Y-m-d'), '02:00:00'); if(empty($check)){ $this->app->erp->CheckProzessstarter('Backup', 'periodisch', 1440, $startTime, 'cronjob', 'backup', 1); }else{ // Backword compatibility $this->app->DB->Update( sprintf( "UPDATE `prozessstarter` SET `bezeichnung` = '%s', `periode`=%d, `recommended_period`=%d, `startzeit`='%s' WHERE `id` = %d", 'Backup', 1440, 1440, $startTime, $check['id'] ) ); } } /** * @param $app * @param $name * @param $erlaubtevars * * @return array */ /*public function TableSearch(&$app, $name, $erlaubtevars) { switch ($name) { case 'backuplist': $allowed['backup'] = array('list'); $heading = array('Name', 'Dateiname', 'Datum', 'Menü'); $width = array('30%', '30%', '20%', '8%'); $findcols = array('name', 'dateiname', 'datum', 'id'); $searchsql = array('name', "DATE_FORMAT(datum, '%d.%m.%Y %H:%i:%s')"); $sql = "SELECT SQL_CALC_FOUND_ROWS id, name, dateiname, DATE_FORMAT(datum, '%d.%m.%Y %H:%i:%s'), id as menu FROM backup"; $defaultorder = 4; //Optional wenn andere Reihenfolge gewuenscht $defaultorderdesc = 1; $where = ""; $count = "SELECT COUNT(id) FROM backup"; $menu = "app->Conf->WFconf['defaulttheme']}/images/backward.svg\" border=\"0\">" . " app->Conf->WFconf['defaulttheme']}/images/download.svg\" border=\"0\">" . " app->Conf->WFconf['defaulttheme']}/images/delete.svg\" border=\"0\">"; break; } $erg = []; foreach ($erlaubtevars as $k => $v) { if(isset($$v)){ $erg[$v] = $$v; } } return $erg; }*/ /** * @void */ public function BackupImporter() { $this->BackupMenu(); if(!empty($cmd = $this->app->Secure->GetGET('cmd')) && $cmd === 'upload'){ /** @var Request $request */ $request = $this->app->Container->get('Request'); /** @var ChunkedUploadRequestHandler $handler */ $handler = $this->app->Container->get('ChunkedUploadRequestHandler'); $tempDir = sys_get_temp_dir(); $saveDir = $this->app->erp->GetRootPath() . '/backup/snapshots/'; if(!file_exists($saveDir) && !mkdir($saveDir, 0777, true) && !is_dir($saveDir)){ throw new RuntimeException(sprintf('Directory "%s" was not created', $saveDir)); } $response = $handler->handleRequest($request, $tempDir, $saveDir); $response->send(); $this->app->erp->ExitWawi(); } if(!empty($cmd = $this->app->Secure->GetGET('cmd')) && $cmd === 'completed'){ if($sFileName = $this->app->Secure->GetPOST('file_name')){ $fileNameExploded = explode('.', $sFileName); array_pop($fileNameExploded); $backupName = implode('.', $fileNameExploded); $address = $this->app->User->GetAdresse(); $this->app->DB->Insert("INSERT INTO backup (adresse, name, dateiname, datum) VALUES ('$address','$backupName','$sFileName',NOW())" ); } $xResponse = [ 'completed' => true, 'message' => $this->app->erp->base64_url_encode('
Backup Import erfolreich abgeschlossen.
') ]; $this->ViewJsonEncode($xResponse); } $this->app->ModuleScriptCache->IncludeWidgetNew('ChunkedUpload'); $this->app->Tpl->Parse('PAGE', "backup_upload.tpl"); } /** * @return void */ public function BackupMenu() { $this->app->erp->MenuEintrag("index.php?module=backup&action=list", "Zurück zur Übersicht"); $this->app->erp->MenuEintrag("index.php?module=backup&action=list", "Übersicht"); //$this->app->erp->MenuEintrag("index.php?module=backup&action=importer", "Importer"); //$this->app->erp->MenuEintrag("BackupModule.createItem()", "Neuer Eintrag"); } /** * @param mixed $xValue * * @return void */ public function ViewJsonEncode($xValue) { header('Content-type: application/json'); echo json_encode($xValue); if(!empty($this->app->erp)){ $this->app->erp->ExitWawi(); } } /** * List all existing Backups * * @return void * @throws Exception */ public function BackupList() { $this->BackupMenu(); $this->app->Tpl->Set('UEBERSCHRIFT', "Backup"); $this->app->Tpl->Set('KURZUEBERSCHRIFT', "Backup"); //$this->app->YUI->TableSearch('TAB1', 'backuplist', 'show', '', '', basename(__FILE__), __CLASS__); $this->app->Tpl->Set('TABTEXT', "Backup"); $this->app->erp->checkActiveCronjob('backup'); /* $processStarterEnabled = 1; $backupConfService = $this->app->Container->get('BackupSystemConfigurationService'); if($backupConfService->tryCheckCronIsEnabled() === false){ $processStarterEnabled = 0; $this->app->Tpl->Set('MESSAGE', '
Es sieht so aus, als ob der Prozessstarter Backup nicht regelmäßig ausgeführt wird! Bitte aktivieren Sie diesen (zu den Prozessstartern)!
'); } // $this->app->Tpl->Set('PROCESS_STARTER_STATUS', $processStarterEnabled); $free = disk_free_space($this->app->erp->GetRootPath()); $free /= 1024 * 1024; $minFree = (int)$this->app->DB->Select( sprintf( "SELECT ROUND(SUM(data_length + index_length) / 1024 / 1024) FROM information_schema.TABLES WHERE table_schema = '%s'", $this->app->Conf->WFdbname ) ) + 512; $userdata = (int)$this->app->erp->GetKonfiguration('userdatasize'); if($free > 0 && $minFree + $userdata > $free){ $this->app->Tpl->Add( 'MESSAGE', sprintf( '
Es ist nur %d MB Speicher auf dem System frei, es werden aber mindestens %d MB für ein Datenbank-Backup benötigt.
', $free, $minFree + $userdata ) ); }*/ $sqlProcessStarter = "SELECT ps.letzteausfuerhung, ps.periode FROM `prozessstarter` AS `ps` WHERE ps.parameter = 'backup' LIMIT 1"; $pStarter = $this->app->DB->SelectRow($sqlProcessStarter); // LATEST BACKUP if($lastestBackup = $this->oBackupGateway->getLatestBackup()){ //$nextRun = (new DateTime($pStarter['letzteausfuerhung']))->getTimestamp() + (int)$pStarter['periode'] * 60; $latestBackupTime = new DateTime($lastestBackup['datum']); $latestMsg = sprintf('Letztes Backup %s Uhr.', $latestBackupTime->format('d.m.Y H:i')); $this->app->Tpl->Add('MESSAGE_DOWNLOAD', '
' . $latestMsg . ' Löschen Herunterladen
'); } $dateTime = new DateTime('yesterday'); $startTime = $dateTime->format('Y-m-d'); $today = (new DateTime())->format('Y-m-d'); $backupFail = null; $startIdSQL = "SELECT cl.id FROM cronjob_log AS `cl` WHERE cl.cronjob_name = 'backup' AND cl.status = 'start' AND (DATE(cl.change_time)='%s' OR DATE(cl.change_time)='%s' )"; $allStartIds = $this->app->DB->SelectFirstCols(sprintf($startIdSQL, $startTime, $today)); if(!empty($allStartIds)){ $failSQL = "SELECT cl.id FROM cronjob_log AS `cl` WHERE cl.status='error' AND cl.parent_id IN (%s) "; $backupFail = $this->app->DB->SelectArr(sprintf($failSQL, implode(',', $allStartIds))); } if($backupFail !== null){ $lastestLogError = ''; $logErrorMsg = ''; $dateError = ''; try { $lastestLogError = $this->GetLogger()->tail(0, null, $this->GetLogger()->getPersistentFileName()); } catch (LogException $exception) { // do nothing } if (!empty($lastestLogError)){ $lastestLogErrorExploded = explode(':', $lastestLogError); $dateError = array_shift($lastestLogErrorExploded); $dateError = sprintf('am %s', date('d.m.Y H:i', $dateError)); $logErrorMsg = sprintf('Grund: %s', implode('', $lastestLogErrorExploded)); } $msgError = sprintf('Das letzte Backup konnte %s nicht erstellt werden. %s', $dateError, $logErrorMsg); $msg = sprintf('
%s
', $msgError); $this->app->Tpl->Add('MESSAGE_ERROR', $msg); } // CHECK ZIP-TOOL if(!$this->oBackupService->hasExecutableExtension('zip')){ if (extension_loaded('zip')) { $msg = sprintf('
%s
', 'Es fehlt das Kommandozeilen-Tool "zip" auf dem Server. Bitte installieren Sie, dass es auf der Kommandozeile verfügbar ist. Es ist nicht die Erweiterung php-zip gemeint, diese ist korrekt vorhanden'); } else{ $msg = sprintf('
%s
', 'Es fehlt das Kommandozeilen-Tool "zip" auf dem Server. Bitte installieren Sie, dass es auf der Kommandozeile verfügbar ist'); } $this->app->Tpl->Add('MESSAGE_ERROR', $msg); } if(!empty($pStarter) && $this->app->erp->isSystemBlockedByBackup()){ $lastRun = new DateTime($pStarter['letzteausfuerhung']); $lastRunMsg = sprintf('Aktuell wird ein Backup (gestartet am %s um %s) erstellt! Bitte warten bis das Backup bereit steht.', $lastRun->format('d.m.Y'), $lastRun->format('H:i')); $msg = sprintf('
%s
', $lastRunMsg); $this->app->Tpl->Add('MESSAGE_RUNNING', $msg); } $this->app->Tpl->Parse('PAGE', 'backup.tpl'); } /** * Creates a Backup * * @return void * @throws Exception */ public function BackupCreate() { $name = $this->app->Secure->GetPOST("name"); $bStatus = false; $message = $this->app->erp->base64_url_encode("
Sie müssen einen Namen für das Datenbank-Backup eingeben.
"); if(!empty($name)){ $adresse = $this->app->User->GetAdresse(); $name = preg_replace('/[^a-zA-Z]+/', '', $name); $dateiname = date('Y-m-d_') . $this->database . '_' . $name . '.' . $this->oBackupService->getArchiveExtension(); if($this->app->DB->Select("SELECT '1' FROM backup WHERE dateiname='$dateiname' LIMIT 1") == '1'){ $message = $this->app->erp->base64_url_encode("
Ein Backup mit diesem Namen existiert bereits.
"); }else{ $sConfig = json_encode( [ 'action' => 'RunCreateJob', 'config' => $this->app->Conf, 'file_name' => $dateiname, 'options' => ['addr' => $adresse, 'name' => $name, 'ssid' => session_id(), 'user_id' => $this->app->User->GetID(), 'ip' => $_SERVER['REMOTE_ADDR']] ]); $bStatus = $this->oBackupService->addToProcessStarter($sConfig); $message = ''; if($bStatus === false){ $message = $this->app->erp->base64_url_encode("
Das Backup kann momentan nicht erstellt werden. Bitte versuchen Sie es später.
"); } } } $ErrorMsg = $this->app->erp->base64_url_encode( "
Das Backup konnte nicht erstellt werden.
" ); $genericSuccess = 'Backup Erstellung gestartet, Bitte warten bis das Backup bereit steht.'; $this->ViewJsonEncode([ 'status' => $bStatus, 'message' => $message, 'generic_error' => $ErrorMsg, 'created_at' => time(), 'backup_file' => $dateiname, 'success_msg' => $this->app->erp->base64_url_encode("
".$genericSuccess."
"), ]); } /** * Deletes an existing Backup * * @return void */ public function BackupDelete() { $id = $this->app->Secure->GetGET("id"); $error = false; if(is_numeric($id)){ $dateiname = $this->app->DB->Select("SELECT dateiname FROM backup WHERE id='$id' LIMIT 1"); if($dateiname != ''){ $backupFile = $this->pfad . $dateiname; if(file_exists($backupFile)){ unlink($backupFile); } // REMOVE META FILE AS WELL $asFile = explode('.', $backupFile); array_pop($asFile); $sMetaFile = implode('.', $asFile) . '.meta'; if(file_exists($sMetaFile)){ unlink($sMetaFile); } $this->app->DB->Delete("DELETE FROM backup WHERE id='$id' LIMIT 1"); $msg = $this->app->erp->base64_url_encode("
Das Backup wurde erfolgreich gelöscht.
"); }else{ $error = true; } }else{ $error = true; } if($error){ $msg = $this->app->erp->base64_url_encode("
Das Backup konnte nicht gelöscht werden.
"); } $this->app->Location->execute("index.php?module=backup&action=list&msg=$msg"); } /** * Download a Backup * * @return void */ public function BackupDownloadSnapshot() { $id = $this->app->Secure->GetGET("id"); if(is_numeric($id)){ $dateiname = $this->app->DB->Select("SELECT dateiname FROM backup WHERE id='$id' LIMIT 1"); $pfad = $this->pfad . $dateiname; if(file_exists($pfad)){ header("Content-Disposition: attachment; filename=$dateiname"); header('Content-Length: ' . filesize($pfad)); $this->readfile_chunked($pfad); //readfile will stream the file. $this->app->ExitXentral(); } $msg = $this->app->erp->base64_url_encode("
Die passende Snapshot Datei konnte nicht gefunden werden!
"); header("Location: ./index.php?module=backup&action=list&msg=$msg"); } } /** * @param $filename * @param bool $retbytes * * @return bool|int */ protected function readfile_chunked($filename, $retbytes = true) { $chunksize = 1 * (1024 * 1024); // how many bytes per chunk $buffer = ''; $cnt = 0; $handle = fopen($filename, 'rb'); if($handle === false){ return false; } while (!feof($handle)) { $buffer = fread($handle, $chunksize); echo $buffer; if($retbytes){ $cnt += strlen($buffer); } } $status = fclose($handle); if($retbytes && $status){ return $cnt; } return $status; } /** * @return BackupLog */ protected function GetLogger() { return $this->app->Container->get('BackupLog'); } /** * Reads running Backup status * * @return void */ public function ReadStatus() { $bStatus = false; $saveDir = $this->app->erp->GetRootPath() . '/backup/snapshots/'; try { $sMessage = $this->GetLogger()->tail(); } catch (LogException $exception) { // Job net yet started $sMessage = ':Please wait'; } $asMessage = explode(':', $sMessage); array_shift($asMessage); $message = implode(' ', $asMessage); if(trim($message) === '--END--'){ $msg = 'Backup-Vorgang erfolgreich abgeschlossen.'; $class = 'error2'; if($sFileName = $this->app->Secure->GetPOST('file_name')){ $asStatusCheckSum = $this->oBackupService->checkSumOnAfterRecovery($sFileName, $this->app->Conf->WFuserdata); if($asStatusCheckSum !== null && !empty($asStatusCheckSum)){ $class = 'warning'; $msg = sprintf('Die folgende(n) Tabelle(n) %s konnten eventuell nicht komplett wiederhergestellt werden!', implode(',', $asStatusCheckSum)); } } $bStatus = true; $this->oBackupService->removeLoggerFiles(); $message = $this->app->erp->base64_url_encode('
' . $msg . '
'); } if(trim($message) === 'ERROR'){ $backupFile = empty($this->app->Secure->GetPOST('backup_file')) ? null : $this->app->Secure->GetPOST('backup_file'); $this->app->erp->setMaintainance(false); $delBackupFile = null !== $backupFile ? $saveDir . $backupFile : null; $this->oBackupService->removeLoggerFiles($delBackupFile); throw new RuntimeException('ERROR'); } // CHECK IF STILL RUNNING if((int)$createdAt = $this->app->Secure->GetPOST('created_at')){ $backupFile = empty($this->app->Secure->GetPOST('backup_file')) ? null : $this->app->Secure->GetPOST('backup_file'); $checkResult = $this->checkFalsePositive($backupFile); if($checkResult){ $msg = 'Backup-Vorgang erfolgreich abgeschlossen.'; $class = 'error2'; $bStatus = true; $this->oBackupService->removeLoggerFiles(); $message = $this->app->erp->base64_url_encode('
' . $msg . '
'); $this->ViewJsonEncode([ 'finished' => $bStatus, 'message' => $message, ]); } $hangCheckStart = 600; // 10min if($bStatus === false && (time() - $createdAt > $hangCheckStart) && $this->app->erp->isSystemBlockedByBackup() === false){ $this->app->erp->setMaintainance(false); $delBackupFile = null !== $backupFile ? $saveDir . $backupFile : null; $this->oBackupService->removeLoggerFiles($delBackupFile); throw new RuntimeException('ERROR: Timeout override'); } } $this->ViewJsonEncode([ 'finished' => $bStatus, 'message' => $message, ]); } private function checkFalsePositive($backupFile = null) { $saveDir = $this->app->erp->GetRootPath() . '/backup/snapshots/'; if(null !== $backupFile){ $dbRecord = $this->app->DB->Select("SELECT '1' FROM backup WHERE dateiname='$backupFile' LIMIT 1") == '1'; if(file_exists($saveDir . $backupFile) && empty($dbRecord)){ // fix it add in db $address = $this->app->User->GetAdresse(); $this->app->DB->Insert("INSERT INTO backup (adresse, name, dateiname, datum) VALUES ('$address',$backupFile,$backupFile,NOW())"); return true; } if(file_exists($saveDir . $backupFile) && !empty($dbRecord)){ return true; } } return false; } /** * Runs the restore backup job * * @param stdClass $oConfig * * @return void */ public function RunRestoreJob($oConfig) { if(!empty($oConfig)){ $this->oBackupService->restore( $this->stdClassToConfig($oConfig->config), $oConfig->file_name, (array)$oConfig->options ); } } /** * Converts stdClass to Config Class * * @param stdClass $config * * @return mixed */ private function stdClassToConfig($config) { if(!($config instanceof stdClass)){ return $config; } return unserialize(sprintf( 'O:%d:"%s"%s', strlen('Config'), 'Config', strstr(strstr(serialize($config), '"'), ':') )); } /** * Runs the create backup job * * @param stdClass $oConfig * * @return void */ public function RunCreateJob($oConfig) { if(!empty($oConfig)){ $this->oBackupService->create($this->stdClassToConfig($oConfig->config), $oConfig->file_name, (array)$oConfig->options); } } /** * Action recover backup * * @return void * @throws Exception */ public function BackupRecover() { $id = (int)$this->app->Secure->GetPOST("id"); if(!empty($id) && ($dateiname = $this->app->DB->Select("SELECT dateiname FROM backup WHERE id='$id' LIMIT 1")) && file_exists($this->oBackupService->getArchivePath($dateiname, $this->app->Conf->WFuserdata))){ if(!empty($cmd = $this->app->Secure->GetGET('cmd')) && $cmd === 'check-meta'){ $this->CheckUserInDump(); } $recoverOption = ['ssid' => session_id(), 'user_id' => $this->app->User->GetID(), 'ip' => $_SERVER['REMOTE_ADDR']]; if($oldDB = $this->app->Secure->GetPOST("old_db")){ $recoverOption['old_dbname'] = $oldDB; } $sConfig = json_encode( [ 'action' => 'RunRestoreJob', 'config' => $this->app->Conf, 'file_name' => $dateiname, 'options' => $recoverOption, ] ); $ErrorMsg = $this->app->erp->base64_url_encode( "
Das Backup konnte nicht wieder hergestellt werden.
" ); $bStatus = $this->oBackupService->addToProcessStarter($sConfig); $message = $bStatus === true ? '' : $this->app->erp->base64_url_encode('
Das Backup kann momentan nicht gestartet werden. Bitte Versuchen Sie später.
'); $xResponse = [ 'status' => $bStatus, 'message' => $message, 'file_name' => $dateiname, 'created_at' => time(), 'generic_error' => $ErrorMsg ]; }else{ $xResponse = [ 'status' => false, 'missing_file' => true, 'message' => $this->app->erp->base64_url_encode('
Backup konnte nicht gefunden werden.
') ]; } $this->ViewJsonEncode($xResponse); } /** * Checks whether the current userId exists in the running restored files * * @return void */ protected function CheckUserInDump() { $xData = null; $bStatus = false; $iUserId = (int)$this->app->User->GetID(); $iBackupId = (int)$this->app->Secure->GetPOST('id'); if(!empty($hData = $this->oBackupGateway->getBackupById($iBackupId)) && ($xData = $this->oBackupService->getDumpMetaData($hData['dateiname'], $this->app->Conf->WFuserdata))){ // Check user admins $bStatus = !empty($xData) && array_key_exists('users', $xData) && in_array($iUserId, $xData['users']); } $message = $bStatus === true ? "Achtung: Es existieren neuere Datensicherungen. Möchten Sie wirklich alle bisherigen Einstellungen löschen/zurücksetzen?\n\nAlle nach diesem Zeitpunkt getätigten Einstellungen und Importvorlagen gehen verloren." : "Achtung: Beim Einspielen des Backups könnten Sie nicht mehr im Stande sein, sich auf dem System anzumelden. Möchten Sie wirklich alle bisherigen Einstellungen löschen / zurücksetzen? Alle nach diesem Zeitpunkt getätigten Einstellungen und Importvorlagen gehen verloren."; $ps_message = $this->SystemHasRunningProcesses() === true ? 'Achtung: Es existieren laufenden Prozesse im System. Mit Ihrer Bestätigung, werden sie beendet.' : ''; $this->ViewJsonEncode(['status' => $bStatus, 'message' => $message, 'ps_message' => $ps_message]); } /** * @return bool */ protected function SystemHasRunningProcesses() { $xData = null; $bStatus = false; if($runningId = $this->app->DB->Select('SELECT id FROM prozessstarter WHERE aktiv=1 AND mutex=1 LIMIT 1')){ $bStatus = true; } return $bStatus; } }