Initial xentral_oss_20.3.c9ffacf

This commit is contained in:
Alex
2021-05-21 08:49:41 +02:00
parent 5406e4a551
commit 34e5ac43d9
18884 changed files with 2109867 additions and 0 deletions
@@ -0,0 +1,47 @@
<?php
namespace Xentral\Modules\DemoExporter;
use ApplicationCore;
use Xentral\Core\DependencyInjection\ContainerInterface;
final class Bootstrap
{
/**
* @return array
*/
public static function registerServices()
{
return [
'DemoExporterService' => 'onInitDemoExporterService',
'DemoExporterGateway' => 'onInitDemoExporterGateway',
];
}
public static function onInitDemoExporterService(ContainerInterface $container)
{
/** @var ApplicationCore $app */
$app = $container->get('LegacyApplication');
return new DemoExporterService(
new DemoExporterDateiService($app),
new DemoExporterCleanerService($app),
$container->get('Database'),
$container->get('BackupSystemConfigurationService'),
$container->get('BackupService'),
$container->get('DemoExporterGateway'),
$container->get('BackupLog')
);
}
/**
* @param ContainerInterface $container
*
* @return DemoExporterGateway
*/
public static function onInitDemoExporterGateway(ContainerInterface $container)
{
return new DemoExporterGateway($container->get('Database'));
}
}
@@ -0,0 +1,85 @@
<?php
namespace Xentral\Modules\DemoExporter;
use ApplicationCore;
use Xentral\Modules\DemoExporter\Exception\DemoExporterCleanerException;
final class DemoExporterCleanerService
{
/**
* @var ApplicationCore
*/
private $app;
/**
*
* @param ApplicationCore $app
*/
public function __construct(ApplicationCore $app)
{
$this->app = $app;
}
/**
* @param string $data
*
* @return string|string[]|null
*/
public function tryXssClean($data)
{
if ($data === null || trim($data) === '') {
throw new DemoExporterCleanerException('Data is missing! ');
}
if ($this->dataNotSQLInjection($data) === false) {
throw new DemoExporterCleanerException('SQL Injection detected! ');
}
return $this->app->stringcleaner->xss_clean($data);
}
/**
* @param string $where
*
* @return bool
*/
private function dataNotSQLInjection($where)
{
$disAllow = [
'UNION',
'SELECT(.*)INTO(.*)',
'INSERT',
'DELETE',
'UPDATE',
'LOAD',
'RENAME',
'DROP',
'CREATE',
'TRUNCATE',
'ALTER',
'COMMIT',
'ROLLBACK',
'MERGE',
'CALL',
'EXPLAIN',
'LOCK',
'GRANT',
'REVOKE',
'SAVEPOINT',
'TRANSACTION',
'SET',
'USE',
'SHOW',
];
$disAllowMapped = array_map(static function ($sqlDialect) {
return '\b' . $sqlDialect . '\b';
}, $disAllow);
$disAllowPattern = implode('|', $disAllowMapped);
return !preg_match("/($disAllowPattern)/i", $where);
}
}
@@ -0,0 +1,44 @@
<?php
namespace Xentral\Modules\DemoExporter;
use ApplicationCore;
use erpAPI;
use Xentral\Modules\DemoExporter\Exception\DemoExporterDateiException;
final class DemoExporterDateiService
{
/** @var erpAPI */
private $erp;
/**
*
* @param ApplicationCore $app
*/
public function __construct(ApplicationCore $app)
{
$this->erp = $app->erp;
}
/**
* @param $dateiId
*
* @return string|string[]|null
*/
public function tryGetDateiPfad($dateiId)
{
if (!is_numeric($dateiId)) {
throw new DemoExporterDateiException('DateiId is missing! ');
}
return $this->erp->GetDateiPfad($dateiId);
}
/**
* @return string|string[]
*/
public function getTmpPath()
{
return $this->erp->GetTMP();
}
}
@@ -0,0 +1,28 @@
<?php
namespace Xentral\Modules\DemoExporter;
use Xentral\Components\Database\Database;
final class DemoExporterGateway
{
private $db;
public function __construct(Database $db)
{
$this->db = $db;
}
/**
* @param string $name
*
* @return array
*/
public function getDemoExporterConfigurationValue($name)
{
return $this->db->fetchRow(
'SELECT k.wert FROM `konfiguration` AS `k` WHERE k.name=:name', ['name' => (string)$name]
);
}
}
@@ -0,0 +1,399 @@
<?php
namespace Xentral\Modules\DemoExporter;
use Exception;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use SplFileInfo;
use stdClass;
use Throwable;
use Xentral\Components\Backup\Logger\BackupLog;
use Xentral\Components\Database\Database;
use Xentral\Components\Database\Exception\DatabaseExceptionInterface;
use Xentral\Components\Database\Exception\QueryFailureException;
use Xentral\Components\Exporter\Csv\CsvExporter;
use Xentral\Components\Exporter\Exception\ExporterExceptionInterface;
use Xentral\Modules\Backup\BackupService;
use Xentral\Modules\Backup\BackupSystemConfigurationService;
use Xentral\Modules\Backup\Exception\RuntimeException as BackupModuleRuntimeException;
use Xentral\Modules\DemoExporter\Exception\DemoExporterCleanerException;
use Xentral\Modules\DemoExporter\Exception\DemoExporterException;
use ZipArchive;
final class DemoExporterService
{
const DEMO_EXPORTER_CONFIG_NAME = 'demo_exporter';
/** @var DemoExporterCleanerService $cleanerService */
private $cleanerService;
/** @var string $tmpDir */
private $tmpDir;
/**
* @var BackupSystemConfigurationService
*/
private $configurationService;
/** @var array $customDemoExporter */
private $customDemoExporter;
/**
* @var BackupService
*/
private $backupService;
/**
* @var DemoExporterGateway
*/
private $gateway;
/**
* @var Database
*/
private $db;
/** @var null|string $sqlTmpArticle */
private $sqlTmpArticle = null;
/**
* @var DemoExporterDateiService
*/
private $dateiService;
/** @var null|string $zipFile */
private $zipFile = null;
/**
* @var BackupLog
*/
private $logger;
/**
* DemoExporterService constructor.
*
* @param DemoExporterDateiService $dateiService
* @param DemoExporterCleanerService $cleanerService
* @param Database $db
* @param BackupSystemConfigurationService $configurationService
* @param BackupService $backupService
* @param DemoExporterGateway $gateway
* @param BackupLog $logger
*/
public function __construct(
DemoExporterDateiService $dateiService,
DemoExporterCleanerService $cleanerService,
Database $db,
BackupSystemConfigurationService $configurationService,
BackupService $backupService,
DemoExporterGateway $gateway,
BackupLog $logger
) {
$this->dateiService = $dateiService;
$this->cleanerService = $cleanerService;
$this->tmpDir = $dateiService->getTmpPath();
$this->db = $db;
$this->configurationService = $configurationService;
$this->backupService = $backupService;
$this->gateway = $gateway;
$this->logger = $logger;
}
/**
* @param array $options
*
* @throws DemoExporterException
* @return void
*/
public function setDumpOptions($options = [])
{
try {
$default = [
'artikel' => 'artikel.geloescht !=1',
];
$options = array_merge($default, $options);
foreach ($options as $table => $where) {
$tableCleaned = $this->cleanerService->tryXssClean($table);
$whereCleaned = '';
if (trim($where) !== '') {
$whereCleaned = $this->cleanerService->tryXssClean($where);
}
$this->customDemoExporter[$tableCleaned] = $whereCleaned;
}
} catch (DemoExporterCleanerException $exception) {
throw new DemoExporterException($exception->getMessage());
}
$value = $this->valueToDB($this->customDemoExporter);
$this->configurationService->trySetConfiguration(static::DEMO_EXPORTER_CONFIG_NAME, $value);
}
/**
* @param array $value
*
* @return string
*/
private function valueToDB($value)
{
return base64_encode(serialize($value));
}
/**
* @param stdClass $config
*
* @return void
*/
public function export(stdClass $config)
{
$this->logger->write('--Begin--');
$customDemoExporter = $this->valueFromDB($config->demo_exporter_config);
$this->zipFile = $config->options->zip_file;
$tmpDirectory = $this->tmpDir . uniqid('', true) . DIRECTORY_SEPARATOR;
if (!@mkdir($tmpDirectory, 0777, true) && !is_dir($tmpDirectory)) {
throw new DemoExporterException(sprintf('Failed to create tmp Dir %s', $tmpDirectory));
}
$this->db->perform("SET SESSION SQL_MODE='ALLOW_INVALID_DATES'");
$this->logger->write('Export DB data');
try {
foreach ($customDemoExporter as $table => $where) {
$tableTmpName = $table . '_' . time();
$sqlTableAndWhere = empty($where) ? $table : $table . ' WHERE ' . stripslashes($where);
$sqlFrom = 'SELECT * FROM ' . $sqlTableAndWhere;
if ($table === 'artikel') {
$articleWhere = !empty($where) ? 'WHERE ' . stripslashes($where) . ' AND ' : 'WHERE ';
$articleWhere .= '(v.gueltig_bis ="0000-00-00" OR v.gueltig_bis >NOW()) AND v.adresse IN(0,NULL)
AND v.gruppe IN(0,NULL) AND artikel.geloescht !=1';
$sqlFrom = 'SELECT artikel.* FROM artikel AS `artikel` INNER JOIN verkaufspreise AS `v`
ON(artikel.id=v.artikel)
' . $articleWhere . '
ORDER BY v.ab_menge';
$tmpSQL = 'CREATE TEMPORARY TABLE IF NOT EXISTS ' . $tableTmpName . ' ' . $sqlFrom;
$this->db->perform($tmpSQL);
$this->sqlTmpArticle = $tableTmpName;
}
$this->sqlToCSV($sqlFrom, $tmpDirectory, $table);
}
} catch (QueryFailureException $exception) {
$this->logger->write('ERROR');
throw new DemoExporterException($exception->getMessage());
} catch (ExporterExceptionInterface $exception) {
$this->logger->write('ERROR');
throw new DemoExporterException($exception->getMessage());
}
$this->logger->write('Grab Data and files');
if (!empty($this->sqlTmpArticle)) {
$this->grabArticleData($this->sqlTmpArticle, $tmpDirectory);
}
if ($this->zipExport($tmpDirectory)) {
$this->logger->write('Create achieve');
$this->deleteDir($tmpDirectory);
}
$this->logger->write('--END--');
}
/**
* @param string $value
*
* @return mixed
*/
private function valueFromDB($value)
{
return unserialize(base64_decode($value));
}
protected function sqlToCSV($sqlFrom, $tmpDirectory, $tableName)
{
$tableName = $tmpDirectory . $tableName . '.csv';
$data = $this->db->yieldAll($sqlFrom);
$exporter = new CsvExporter();
$exporter->export($tableName, $data);
}
/**
* @param $tableTmpName
* @param $tmpDirectory
*/
private function grabArticleData($tableTmpName, $tmpDirectory)
{
if (empty($tableTmpName)) {
throw new DemoExporterException('Table for grabbing is missing');
}
$sql = 'SELECT a.id, dv.dateiname, dv.version, a.nummer FROM ' . $tableTmpName . ' AS `a`
LEFT JOIN datei_stichwoerter AS `ds` ON (a.id=ds.parameter)
INNER JOIN datei AS `d` ON (ds.datei=d.id)
LEFT JOIN datei_version AS `dv` ON (d.id=dv.datei)
WHERE LOWER(ds.objekt) =:object AND d.geloescht !=:deleted AND a.geloescht !=:deleted';
// SQL verkaufpreise
$salePrices = 'SELECT v.* FROM ' . $tableTmpName . ' AS `a` INNER JOIN verkaufspreise AS `v` ON(a.id=v.artikel)
WHERE (v.gueltig_bis ="0000-00-00" OR v.gueltig_bis >NOW()) AND v.adresse IN(0,NULL)
AND v.gruppe IN(0,NULL) ORDER BY v.ab_menge';
$this->sqlToCSV($salePrices, $tmpDirectory, 'verkaufspreise');
try {
$rows = $this->db->fetchAll($sql, ['object' => 'artikel', 'deleted' => 1]);
} catch (DatabaseExceptionInterface $exception) {
throw new DemoExporterException($exception->getMessage());
}
$filesDir = $tmpDirectory . 'files' . DIRECTORY_SEPARATOR;
if (!@mkdir($filesDir, 0777, true) && !is_dir($filesDir)) {
throw new DemoExporterException(sprintf('Failed to create tmp Dir %s', $filesDir));
}
foreach ($rows as $row) {
$id = $row['id'];
$articleNumber = $row['nummer'];
$fileName = $row['dateiname'];
$pathFile = $this->dateiService->tryGetDateiPfad($id);
$finalName = $filesDir . $articleNumber . '_' . $fileName;
if (file_exists($pathFile) && !copy($pathFile, $finalName)) {
$this->logger->write('ERROR');
throw new DemoExporterException(sprintf('Failed to copy %s into tmp Dir %s', $pathFile, $finalName));
}
}
$this->db->perform('DROP TABLE IF EXISTS ' . $tableTmpName);
}
/**
* @param string $tmpDirectory
*
* @throws DemoExporterException
*
* @return bool
*/
private function zipExport($tmpDirectory)
{
$rootPath = realpath($tmpDirectory);
$oZip = new ZipArchive();
$zipFile = $this->tmpDir . $this->zipFile;
if ($this->openZipObject($oZip, $zipFile, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== true) {
$this->logger->write('ERROR');
throw new DemoExporterException(sprintf('Failure to create temporary file in "%s"', $this->tmpDir));
}
try {
/** @var RecursiveIteratorIterator $oFiles */
$oFiles = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($rootPath),
RecursiveIteratorIterator::LEAVES_ONLY
);
foreach ($oFiles as $name => $oFile) {
/** @var SplFileInfo $oFile */
if (!$oFile->isDir()) {
$filePath = $oFile->getRealPath();
$relativePath = substr($filePath, strlen($rootPath) + 1);
$oZip->addFile($filePath, $relativePath);
}
}
return $oZip->close();
} catch (Throwable $exception) {
$this->logger->write('ERROR');
throw new DemoExporterException($exception->getMessage());
}
}
/**
* @param ZipArchive $oZip
* @param string $fileName
* @param int $flags
*
* @return mixed
*/
protected function openZipObject($oZip, $fileName, $flags = 0)
{
return $oZip->open($fileName, $flags);
}
/**
* @param string $dirPath
*
* @return bool
*/
private function deleteDir($dirPath)
{
if (is_dir($dirPath)) {
if (substr($dirPath, strlen($dirPath) - 1, 1) !== '/') {
$dirPath .= '/';
}
$files = glob($dirPath . '*', GLOB_MARK);
foreach ($files as $file) {
if (is_dir($file)) {
$this->deleteDir($file);
} else {
unlink($file);
}
}
return rmdir($dirPath);
}
$this->logger->write('ERROR');
throw new DemoExporterException(sprintf('Deleted DIR %s failed', $dirPath));
}
/**
* @param string $zipFileName
*
* @throws DemoExporterException
*
* @return string
*/
public function getZippedFile($zipFileName)
{
$zippedFile = $this->tmpDir . $zipFileName;
if (!file_exists($zippedFile)) {
$this->logger->write('ERROR');
throw new DemoExporterException(sprintf('Zipped file %s cannot be found!', $zipFileName));
}
return $zippedFile;
}
/**
* @param array $xConfig
* @param string $identifier
* @param string $sParam
* @param string $cronFile
*
* @throws Exception
*/
public function addToProcessStarter(
$xConfig,
$identifier = 'Demo Exporter Konfigurartion',
$sParam = 'demo_exporter_cron',
$cronFile = 'demo_exporter'
) {
try {
$rowDemoExporter = $this->gateway->getDemoExporterConfigurationValue(static::DEMO_EXPORTER_CONFIG_NAME);
if (empty($rowDemoExporter)) {
$this->logger->write('ERROR');
throw new DemoExporterException(
sprintf('configuration value %s cannot be found !', static::DEMO_EXPORTER_CONFIG_NAME)
);
}
$xConfig['demo_exporter_config'] = $rowDemoExporter['wert'];
$value = json_encode($xConfig);
$this->backupService->addToProcessStarter($value, $identifier, $sParam, $cronFile);
} catch (BackupModuleRuntimeException $exception) {
$this->logger->write('ERROR');
throw new DemoExporterException($exception->getMessage());
}
}
}
@@ -0,0 +1,8 @@
<?php
namespace Xentral\Modules\DemoExporter\Exception;
class DemoExporterCleanerException extends DemoExporterException
{
}
@@ -0,0 +1,7 @@
<?php
namespace Xentral\Modules\DemoExporter\Exception;
class DemoExporterDateiException extends DemoExporterException
{
}
@@ -0,0 +1,10 @@
<?php
namespace Xentral\Modules\DemoExporter\Exception;
use RuntimeException as SplRuntimeException;
class DemoExporterException extends SplRuntimeException implements DemoExporterExceptionInterface
{
}
@@ -0,0 +1,11 @@
<?php
namespace Xentral\Modules\DemoExporter\Exception;
use Xentral\Core\Exception\ModuleExceptionInterface;
interface DemoExporterExceptionInterface extends ModuleExceptionInterface
{
}
@@ -0,0 +1,24 @@
#add-more-table {
float: right;
background: var(--green);
color: #fff;
font-weight: bold;
padding: 7px 7px 5px;
border: none;
border-radius: 4px;
margin-top: 4px;
cursor: pointer;
margin-bottom: 0;
margin-right: 15px;
}
.remove-me{cursor: pointer;}
.demo-exporter-status-message{
position: absolute;
width: auto;
height: auto;
text-align: center;
left: 50%;
transform: translate(-50%, -70%);
opacity: 1;
}
@@ -0,0 +1,313 @@
var DemoExporterModule = function ($) {
'use strict';
var me = {
isInitialized: false,
storage: {
$backupDialog: null,
$createItemDialog: null,
$demoExporterDialog: null
},
/**
* @return void
*/
init: function () {
if (me.isInitialized === true) {
return;
}
me.storage.$demoExporterDialog = $('#demo-exporter-dialog');
me.storage.$createItemDialog = $('#add-dump-configurator');
me.addDialog();
me.demoDialog();
me.registerEvents();
me.isInitialized = true;
},
registerEvents: function () {
$('#add-more-table').on('click', function (e) {
e.preventDefault();
me.addNewFields();
});
$(document).on('click', '.remove-me', function (e) {
e.preventDefault();
me.removeField(this);
});
$(document).on('click', '#delete-demo-exporter', function (e) {
e.preventDefault();
me.delete();
});
$(document).on('click', '#download-demo-exporter', function (e) {
e.preventDefault();
me.removeDemoCache('file_name');
me.removeDemoCache('refresh_id');
if (me.hasProcessStarterEnabled() === true) {
me.export();
} else {
me.showProcessStarterMissingError();
}
});
},
/**
* @return {void}
*/
addDialog: function () {
me.storage.$createItemDialog.dialog({
modal: true,
bgiframe: true,
closeOnEscape: false,
minWidth: 650,
maxHeight: 700,
autoOpen: false,
buttons: {
ABBRECHEN: function () {
me.resetAdd();
$(this).dialog('close');
},
SPEICHERN: function () {
me.saveItem();
$(this).dialog('close');
}
}
});
},
/**
* @return {void}
*/
demoDialog: function () {
me.storage.$demoExporterDialog.dialog({
modal: true,
bgiframe: true,
closeOnEscape: false,
minWidth: 650,
maxHeight: 700,
autoOpen: false,
buttons: {
ABBRECHEN: function () {
me.resetAdd();
$(this).dialog('close');
}
}
});
},
/**
* @return {void}
*/
saveItem: function () {
var $form = $('#dump-configurator-form');
$form.action = 'index.php?module=demoexporter&action=create';
$form.submit();
},
/**
* @return void
*/
resetAdd: function () {
$('.geklonnt').remove();
$('#add-dump-configurator').find('.d_sql').val('');
},
/**
* @return {void}
*/
createDump: function () {
if (me.isInitialized === false) {
me.init();
}
me.resetAdd();
me.storage.$createItemDialog.dialog('open');
},
addNewFields: function () {
$('#configurator-container tbody').append('<tr class=\'geklonnt\'>' +
'<td> <label><strong>Table name</strong></label>' +
'<input type="text" name="table[]" class="d_table" size="20" placeholder="artikel" required></td>' +
'<td><label><strong>Where Kondition</strong></label>' +
'<textarea name="where[]" class="d_sql" rows="5" cols="50"></textarea></td>' +
'<td class=\'remove-me\'> [-] </td>' +
'</tr> ');
},
removeField: function (src) {
var $tr = $(src).closest('tr');
$tr.remove();
},
delete: function () {
var value = 'index.php?module=demoexporter&action=delete';
if (!confirm('Soll der Eintrag wirklich gelöscht oder storniert werden?')) {
return false;
} else {
window.location.href = value;
}
},
export: function () {
var refreshId = me.setInterval();
me.setDemoCache('refresh_id', refreshId);
$('#demoExporterModalTimer').removeClass('hide').loadingOverlay('show').dialog({
modal: true, minWidth: 1200, resizable: false, closeOnEscape: false,
dialogClass: 'no-titlebar',
open: function (event, ui) {
$('.ui-dialog-titlebar').hide();
$('#demoExporterModalTimer').css({'overflow': 'hidden'});
}
});
$.ajax({
url: 'index.php?module=demoexporter&action=export',
data: {},
method: 'get',
dataType: 'json',
success: function (data) {
if (data.status === false) {
clearInterval(refreshId);
me.reloadUrl(data.message);
}
me.setDemoCache('generic_error', data.generic_error);
me.setDemoCache('file_name', data.file_name);
},
error: function ($xhr, textStatus, errorThrown) {
alert('Demo konnte nicht exportiert werden');
}
});
},
/**
* @return {void}
*/
readStatus: function () {
var fileName = me.getCacheDemoValue('file_name');
var sData = {};
if (fileName != null) {
sData = {'file_name': fileName};
}
$.ajax({
url: 'index.php?module=demoexporter&action=readstatus',
data: sData,
method: 'post',
dataType: 'json',
success: function (data) {
if (data.finished === true) {
var refreshId = me.getCacheDemoValue('refresh_id');
clearInterval(refreshId);
$('#demoExporterModalTimer').addClass('hide').loadingOverlay('remove').dialog('close');
var reloadLink = 'index.php?module=demoexporter&action=list&cmd=download&file=' + fileName;
me.reloadUrl(reloadLink);
return;
}
if ($('.demo-exporter-status-message').length > 0) {
if ($('.demo-exporter-status-message').hasClass('hide')) {
$('.demo-exporter-status-message').removeClass('hide');
}
if (data.finished === false && $('#live-status').length > 0) {
$('#live-status').html($.trim(data.message) + ' ...');
}
} else {
$('#demoExporterModalTimer div.loading-back').after(
'<div class="demo-exporter-status-message hide"><p id="live-status"></p></div>');
}
//console.log(data);
},
error: function ($xhr, textStatus, errorThrown) {
var interValId = me.getCacheDemoValue('refresh_id');
var genericErrorMsg = me.getCacheDemoValue('generic_error');
if (interValId) {
clearInterval(interValId);
}
var errorReloadLink = 'index.php?module=demoexporter&action=list&msg=' + genericErrorMsg;
me.reloadUrl(errorReloadLink);
}
});
},
/**
* @return {number} refreshId
*/
setInterval: function () {
return setInterval(function () {
me.readStatus();
}, 5000);
},
/**
* @param {string} data
* @return {string}|{null}
*/
getCacheDemoValue: function (data) {
return typeof $('#demo-exporter-dialog').data(data) !== 'undefined' ?
$('#demo-exporter-dialog').attr('data-' + data) : null;
},
/**
*
* @param {string} data
* @param {number} value
* @param {string} value
*/
setDemoCache: function (data, value) {
$('#demo-exporter-dialog').attr('data-' + data, value);
},
/**
* @param {string} data
*/
removeDemoCache: function (data) {
$('#demo-exporter-dialog').removeAttr(data);
},
/**
*Reloads current page with parameter message
* @param {string} value
*/
reloadUrl: function (value) {
window.location.href = value;
},
/**
* @return {boolean}
*/
hasProcessStarterEnabled: function () {
var $demoModalStorage = $('#demo-exporter-dialog');
return parseInt($demoModalStorage.data('ps')) === 1;
},
/**
* @return {void}
*/
showProcessStarterMissingError: function () {
var message = 'Es sieht so aus, als ob der Prozessstarter Cronjob nicht regelm&auml;&szlig;ig ' +
'ausgef&uuml;hrt wird! Bitte aktivieren Sie diesen ' +
'(<a href="http://helpdesk.wawision.de/doku.php?id=entwickler:grundinstallation#einrichten_des_heartbeat-cronjobs_optional" target="_blank">Link zu Helpdesk</a>)!';
me.storage.$demoExporterDialog.dialog('open');
$('#demo-exporter-message').addClass('error').html(message);
}
};
return {
init: me.init,
createDump: me.createDump
};
}(jQuery);
$(function () {
if ($('#add-dump-configurator').length > 0) {
DemoExporterModule.init();
}
});