Newer
Older
<?php
/**
* Copyright Maarch since 2008 under licence GPLv3.
* See LICENCE.txt file at the root folder for more details.
* This file is part of Maarch software.
*/
/**
* @brief Installer Controller
*
* @author dev@maarch.org
*/
namespace SrcCore\controllers;
use Slim\Http\Request;
use Slim\Http\Response;
use SrcCore\models\AuthenticationModel;
use SrcCore\models\DatabaseModel;
use SrcCore\models\DatabasePDO;
class InstallerController
{
public function getPrerequisites(Request $request, Response $response)
{
$phpVersion = phpversion();
$phpVersionValid = (version_compare(PHP_VERSION, '7.2') >= 0);
exec('whereis unoconv', $output, $return);
$output = explode(':', $output[0]);
$unoconv = !empty($output[1]);
exec('whereis wkhtmltopdf', $outputWk, $returnWk);
$outputWk = explode(':', $outputWk[0]);
$wkhtmlToPdf = !empty($outputWk[1]);
exec('whereis netcat', $outputNetcat, $returnNetcat);
$outputNetcat = explode(':', $outputNetcat[0]);
exec('whereis nmap', $outputNmap, $returnNmap);
$outputNmap = explode(':', $outputNmap[0]);
$netcatOrNmap = !empty($outputNetcat[1]) || !empty($outputNmap[1]);
$pdoPgsql = @extension_loaded('pdo_pgsql');
$pgsql = @extension_loaded('pgsql');
$mbstring = @extension_loaded('mbstring');
$fileinfo = @extension_loaded('fileinfo');
$gd = @extension_loaded('gd');
$imagick = @extension_loaded('imagick');
$gettext = @extension_loaded('gettext');
$curl = @extension_loaded('curl');
$zip = @extension_loaded('zip');
$json = @extension_loaded('json');
$xml = @extension_loaded('xml');
$writable = is_writable('.') && is_readable('.');
$displayErrors = (ini_get('display_errors') == '1');
$errorReporting = CoreController::getErrorReportingFromPhpIni();
$errorReporting = !in_array(8, $errorReporting);
$prerequisites = [
'phpVersion' => $phpVersion,
'unoconv' => $unoconv,
'pdoPgsql' => $pdoPgsql,
'pgsql' => $pgsql,
'mbstring' => $mbstring,
'fileinfo' => $fileinfo,
'gd' => $gd,
'imagick' => $imagick,
'gettext' => $gettext,
'curl' => $curl,
'zip' => $zip,
'writable' => $writable,
'displayErrors' => $displayErrors,
];
return $response->withJson(['prerequisites' => $prerequisites]);
}
public function checkDatabaseConnection(Request $request, Response $response)
{
$queryParams = $request->getQueryParams();
if (!Validator::stringType()->notEmpty()->validate($queryParams['server'])) {
return $response->withStatus(400)->withJson(['errors' => 'QueryParams server is empty or not a string']);
} elseif (!Validator::intVal()->notEmpty()->validate($queryParams['port'])) {
return $response->withStatus(400)->withJson(['errors' => 'QueryParams port is empty or not an integer']);
} elseif (!Validator::stringType()->notEmpty()->validate($queryParams['user'])) {
return $response->withStatus(400)->withJson(['errors' => 'QueryParams user is empty or not a string']);
} elseif (!Validator::stringType()->notEmpty()->validate($queryParams['password'])) {
return $response->withStatus(400)->withJson(['errors' => 'QueryParams password is empty or not a string']);
} elseif (!Validator::stringType()->notEmpty()->validate($queryParams['name'])) {
return $response->withStatus(400)->withJson(['errors' => 'QueryParams name is empty or not a string']);
} elseif (!Validator::length(1, 50)->validate($queryParams['name'])) {
return $response->withStatus(400)->withJson(['errors' => 'QueryParams name length is not valid']);
} elseif (strpbrk($queryParams['name'], '";') !== false) {
return $response->withStatus(400)->withJson(['errors' => 'QueryParams name is not valid']);

Damien
committed
$options = [
\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
\PDO::ATTR_CASE => \PDO::CASE_NATURAL
];

Damien
committed
$firstTry = true;
$dsn = "pgsql:host={$queryParams['server']};port={$queryParams['port']};dbname={$queryParams['name']}";
try {
$db = new \PDO($dsn, $queryParams['user'], $queryParams['password'], $options);
} catch (\PDOException $PDOException) {
$firstTry = false;
$dsn = "pgsql:host={$queryParams['server']};port={$queryParams['port']};dbname=postgres";
try {
$db = new \PDO($dsn, $queryParams['user'], $queryParams['password'], $options);
} catch (\PDOException $PDOException) {
return $response->withStatus(400)->withJson(['errors' => 'Database connection failed']);
}

Damien
committed
if ($firstTry) {
$query = $db->query("SELECT table_name FROM information_schema.tables WHERE table_schema not in ('pg_catalog', 'information_schema')");
$row = $query->fetch(\PDO::FETCH_ASSOC);
if (!empty($row)) {
return $response->withStatus(400)->withJson(['errors' => 'Given database has tables']);
}
return $response->withJson(['warning' => 'Database exists']);
return $response->withStatus(204);
}
public function getSQLDataFiles(Request $request, Response $response)
{
$dataFiles = [];
$sqlFiles = scandir('sql');
foreach ($sqlFiles as $sqlFile) {
if ($sqlFile == '.' || $sqlFile == '..') {
continue;
}
if (strpos($sqlFile, 'data_') === 0) {
$dataFiles[] = str_replace('.sql', '', $sqlFile);
}
}
return $response->withJson(['dataFiles' => $dataFiles]);
}

Damien
committed
public function checkDocservers(Request $request, Response $response)
{
$queryParams = $request->getQueryParams();
if (!Validator::stringType()->notEmpty()->validate($queryParams['path'])) {
return $response->withStatus(400)->withJson(['errors' => 'Queryparams path is empty or not a string']);
} elseif (strpbrk($queryParams['path'], '"\'<>|*:?') !== false) {
return $response->withStatus(400)->withJson(['errors' => 'Body path is not valid']);

Damien
committed
}
$path = urldecode($queryParams['path']);
$path = preg_replace('/\/{2,}/', '/', $path);

Damien
committed
$path = rtrim($path, '/');
$multiplesPaths = explode('/', $path);
while (!empty($multiplesPaths)) {
$pathToTest = implode('/', $multiplesPaths);
if (empty($pathToTest)) {
$pathToTest = '/';
}
if (is_dir($pathToTest)) {
if (!is_readable($pathToTest) || !is_writable($pathToTest)) {
return $response->withStatus(400)->withJson(['errors' => "Queryparams path is not readable or writable"]);
}
break;
}
unset($multiplesPaths[count($multiplesPaths) - 1]);
}
return $response->withStatus(204);
}
public function checkCustomName(Request $request, Response $response)
{
$queryParams = $request->getQueryParams();
if (!Validator::stringType()->notEmpty()->validate($queryParams['customId'])) {
return $response->withStatus(400)->withJson(['errors' => 'Queryparams customId is empty or not a string']);
} elseif (!preg_match('/^[a-zA-Z0-9_\-]*$/', $queryParams['customId'])) {
return $response->withStatus(400)->withJson(['errors' => 'Queryparams customId has unauthorized characters']);
}
if (is_dir("custom/{$queryParams['customId']}")) {
return $response->withStatus(400)->withJson(['errors' => "Custom already exists"]);
}
$customFile = CoreConfigModel::getJsonLoaded(['path' => 'custom/custom.json']);
if (!empty($customFile)) {
foreach ($customFile as $value) {
if ($value['id'] == $queryParams['customId']) {
return $response->withStatus(400)->withJson(['errors' => "Custom already exists in custom.json"]);
}
}
}
public function createCustom(Request $request, Response $response)
{
$body = $request->getParsedBody();
if (!Validator::stringType()->notEmpty()->validate($body['customId'])) {
return $response->withStatus(400)->withJson(['errors' => 'Body customId is empty or not a string']);
} elseif (!preg_match('/^[a-zA-Z0-9_\-]*$/', $body['customId'])) {
return $response->withStatus(400)->withJson(['errors' => 'Body customId has unauthorized characters']);
if (is_dir("custom/{$body['customId']}")) {
return $response->withStatus(400)->withJson(['errors' => 'Custom with this name already exists']);
} elseif (!@mkdir("custom/{$body['customId']}/apps/maarch_entreprise/xml", 0755, true)) {
return $response->withStatus(400)->withJson(['errors' => 'Custom folder creation failed']);
}
if (!is_file("custom/custom.json")) {
$fp = fopen('custom/custom.json', 'w');
fwrite($fp, json_encode([], JSON_PRETTY_PRINT));
fclose($fp);
}
$customFile = CoreConfigModel::getJsonLoaded(['path' => 'custom/custom.json']);
$customFile[] = [
];
$fp = fopen('custom/custom.json', 'w');
fwrite($fp, json_encode($customFile, JSON_PRETTY_PRINT));
fclose($fp);
$jsonFile = [
'config' => [
'lang' => $body['lang'] ?? 'fr',
'applicationName' => $body['applicationName'] ?? $body['customId'],
'timezone' => 'Europe/Paris',
'maarchDirectory' => realpath('.') . '/',
'customID' => $body['customId'],
'maarchUrl' => ''
$fp = fopen("custom/{$body['customId']}/apps/maarch_entreprise/xml/config.json", 'w');
fwrite($fp, json_encode($jsonFile, JSON_PRETTY_PRINT));
fclose($fp);
$cmd = 'ln -s ' . realpath('.') . "/ {$body['customId']}";
file_put_contents("custom/{$body['customId']}/initializing.lck", 1);
return $response->withStatus(204);
}
public function createDatabase(Request $request, Response $response)
{
$body = $request->getParsedBody();
if (!Validator::stringType()->notEmpty()->validate($body['server'])) {
return $response->withStatus(400)->withJson(['errors' => 'Body server is empty or not a string']);
} elseif (!Validator::intVal()->notEmpty()->validate($body['port'])) {
return $response->withStatus(400)->withJson(['errors' => 'Body port is empty or not an integer']);
} elseif (!Validator::stringType()->notEmpty()->validate($body['user'])) {
return $response->withStatus(400)->withJson(['errors' => 'Body user is empty or not a string']);
} elseif (!Validator::stringType()->notEmpty()->validate($body['password'])) {
return $response->withStatus(400)->withJson(['errors' => 'Body password is empty or not a string']);
} elseif (!Validator::stringType()->notEmpty()->validate($body['name'])) {
return $response->withStatus(400)->withJson(['errors' => 'Body name is empty or not a string']);
} elseif (!Validator::length(1, 50)->validate($body['name'])) {
return $response->withStatus(400)->withJson(['errors' => 'Body name length is not valid']);
} elseif (strpbrk($body['name'], '";') !== false) {
return $response->withStatus(400)->withJson(['errors' => 'Body name is not valid']);
} elseif (!Validator::stringType()->notEmpty()->validate($body['customId'])) {
return $response->withStatus(400)->withJson(['errors' => 'Body customId is empty or not a string']);
} elseif (!is_file("custom/{$body['customId']}/initializing.lck")) {
return $response->withStatus(403)->withJson(['errors' => 'Custom is already installed']);
} elseif (!is_file("custom/{$body['customId']}/apps/maarch_entreprise/xml/config.json")) {
return $response->withStatus(400)->withJson(['errors' => 'Custom does not exist']);
$options = [
\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
\PDO::ATTR_CASE => \PDO::CASE_NATURAL
];
$connection = "host={$body['server']} port={$body['port']} user={$body['user']} password={$body['password']} dbname={$body['name']}";
$connected = @pg_connect($connection);
if ($connected) {
pg_close();
$dsn = "pgsql:host={$body['server']};port={$body['port']};dbname={$body['name']}";
$db = new \PDO($dsn, $body['user'], $body['password'], $options);
$query = $db->query("SELECT table_name FROM information_schema.tables WHERE table_schema not in ('pg_catalog', 'information_schema')");
$row = $query->fetch(\PDO::FETCH_ASSOC);
if (!empty($row)) {
return $response->withStatus(400)->withJson(['errors' => 'Given database has tables']);
}
} else {
$dsn = "pgsql:host={$body['server']};port={$body['port']};dbname=postgres";
try {
$db = new \PDO($dsn, $body['user'], $body['password'], $options);
} catch (\PDOException $PDOException) {
return $response->withStatus(400)->withJson(['errors' => 'Database connection failed']);
}
$db->query("CREATE DATABASE \"{$body['name']}\" WITH TEMPLATE template0 ENCODING = 'UTF8'");
$db->query("ALTER DATABASE \"{$body['name']}\" SET DateStyle =iso, dmy");
$dsn = "pgsql:host={$body['server']};port={$body['port']};dbname={$body['name']}";
$db = new \PDO($dsn, $body['user'], $body['password'], $options);
if (!is_file('sql/structure.sql')) {
return $response->withStatus(400)->withJson(['errors' => 'File sql/structure.sql does not exist']);
$fileContent = file_get_contents('sql/structure.sql');
$result = $db->exec($fileContent);
if ($result === false) {
return $response->withStatus(400)->withJson(['errors' => 'Request failed : run structure.sql']);
}
if (!empty($body['data'])) {
if (!is_file("sql/{$body['data']}.sql")) {
return $response->withStatus(400)->withJson(['errors' => "File sql/{$body['data']}.sql does not exist"]);

Damien
committed
$fileContent = file_get_contents("sql/{$body['data']}.sql");
$result = $db->exec($fileContent);
if ($result === false) {
return $response->withStatus(400)->withJson(['errors' => "Request failed : run {$body['data']}.sql"]);
}
}
$configFile = CoreConfigModel::getJsonLoaded(['path' => "custom/{$body['customId']}/apps/maarch_entreprise/xml/config.json"]);
$configFile['database'] = [
[
"server" => $body['server'],
"port" => $body['port'],
"type" => 'POSTGRESQL',
"name" => $body['name'],
"user" => $body['user'],
"password" => $body['password']
]
];
$fp = fopen("custom/{$body['customId']}/apps/maarch_entreprise/xml/config.json", 'w');
fwrite($fp, json_encode($configFile, JSON_PRETTY_PRINT));
fclose($fp);
return $response->withStatus(204);
}
public function createDocservers(Request $request, Response $response)
{
$body = $request->getParsedBody();
if (!Validator::stringType()->notEmpty()->validate($body['path'])) {
return $response->withStatus(400)->withJson(['errors' => 'Body path is empty or not a string']);

Damien
committed
} elseif (!Validator::stringType()->notEmpty()->validate($body['customId'])) {
return $response->withStatus(400)->withJson(['errors' => 'Body customId is empty or not a string']);
} elseif (!is_file("custom/{$body['customId']}/initializing.lck")) {
return $response->withStatus(403)->withJson(['errors' => 'Custom is already installed']);
} elseif (!is_file("custom/{$body['customId']}/apps/maarch_entreprise/xml/config.json")) {
return $response->withStatus(400)->withJson(['errors' => 'Custom does not exist']);
} elseif (strpbrk($body['path'], '"\'<>|*:?') !== false) {
return $response->withStatus(400)->withJson(['errors' => 'Body path is not valid']);
$body['path'] = preg_replace('/\/{2,}/', '/', $body['path']);

Damien
committed
$body['path'] = rtrim($body['path'], '/');
if (!is_dir($body['path'])) {
if (!@mkdir($body['path'], 0755, true)) {
return $response->withStatus(400)->withJson(['errors' => "Folder creation failed for path : {$body['path']}"]);
}
} elseif (!is_readable($body['path']) || !is_writable($body['path'])) {
return $response->withStatus(400)->withJson(['errors' => "Body path is not readable or writable"]);
}
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
$docservers = [
'AI' => 'ai',
'RESOURCES' => 'resources',
'ATTACHMENTS' => 'attachments',
'CONVERT_RESOURCES' => 'convert_resources',
'CONVERT_ATTACH' => 'convert_attachments',
'TNL_RESOURCES' => 'thumbnails_resources',
'TNL_ATTACHMENTS' => 'thumbnails_attachments',
'FULLTEXT_RESOURCES' => 'fulltext_resources',
'FULLTEXT_ATTACHMENTS' => 'fulltext_attachments',
'TEMPLATES' => 'templates',
'ARCHIVETRANSFER' => 'archive_transfer',
'ACKNOWLEDGEMENT_RECEIPTS' => 'acknowledgement_receipts'
];
foreach ($docservers as $docserver) {
if (!@mkdir("{$body['path']}/{$body['customId']}/{$docserver}", 0755, true)) {
return $response->withStatus(400)->withJson(['errors' => "Docserver folder creation failed for path : {$body['path']}/{$body['customId']}/{$docserver}"]);
}
}
$templatesPath = "{$body['path']}/{$body['customId']}/templates/0000";
if (!@mkdir($templatesPath, 0755, true)) {
return $response->withJson(['success' => "Docservers created but templates folder creation failed"]);
}
$templatesToCopy = scandir('install/templates/0000');
foreach ($templatesToCopy as $templateToCopy) {
if ($templateToCopy == '.' || $templateToCopy == '..') {
continue;
}
copy("install/templates/0000/{$templateToCopy}", "{$templatesPath}/{$templateToCopy}");
}
DatabasePDO::reset();
new DatabasePDO(['customId' => $body['customId']]);
DatabaseModel::update([
'table' => 'docservers',
'postSet' => ['path_template' => "replace(path_template, '/opt/maarch/docservers', '{$body['path']}/{$body['customId']}')"],
'where' => ['1 = 1']
]);
return $response->withStatus(204);
}
public function createCustomization(Request $request, Response $response)
{
$body = $request->getParsedBody();
if (!Validator::stringType()->notEmpty()->validate($body['bodyLoginBackground'])) {
return $response->withStatus(400)->withJson(['errors' => 'Body bodyLoginBackground is empty']);
} elseif (!Validator::stringType()->notEmpty()->validate($body['logo'])) {
return $response->withStatus(400)->withJson(['errors' => 'Body logo is empty']);
} elseif (!Validator::stringType()->notEmpty()->validate($body['customId'])) {
return $response->withStatus(400)->withJson(['errors' => 'Body customId is empty or not a string']);
} elseif (!preg_match('/^[a-zA-Z0-9_\-]*$/', $body['customId'])) {
return $response->withStatus(400)->withJson(['errors' => 'Body customId has unauthorized characters']);
} elseif (!is_file("custom/{$body['customId']}/initializing.lck")) {
return $response->withStatus(403)->withJson(['errors' => 'Custom is already installed']);
mkdir("custom/{$body['customId']}/img", 0755, true);
if (strpos($body['bodyLoginBackground'], 'data:image/jpeg;base64,') === false) {
if (!is_file("dist/{$body['bodyLoginBackground']}")) {
return $response->withStatus(400)->withJson(['errors' => 'Body bodyLoginBackground does not exist']);
copy("dist/{$body['bodyLoginBackground']}", "custom/{$body['customId']}/img/bodylogin.jpg");
} else {
$tmpPath = CoreConfigModel::getTmpPath();
$tmpFileName = $tmpPath . 'installer_body_' . rand() . '_file.jpg';
$body['bodyLoginBackground'] = str_replace('data:image/jpeg;base64,', '', $body['bodyLoginBackground']);
$file = base64_decode($body['bodyLoginBackground']);
file_put_contents($tmpFileName, $file);
$size = strlen($file);
$imageSizes = getimagesize($tmpFileName);

Damien
committed
if ($imageSizes[0] < 1920 || $imageSizes[1] < 1080) {
return $response->withStatus(400)->withJson(['errors' => 'BodyLogin image is not wide enough']);
} elseif ($size > 10000000) {
return $response->withStatus(400)->withJson(['errors' => 'BodyLogin size is not allowed']);
}
copy($tmpFileName, "custom/{$body['customId']}/img/bodylogin.jpg");
}
if (strpos($body['logo'], 'data:image/svg+xml;base64,') !== false) {
$tmpPath = CoreConfigModel::getTmpPath();
$tmpFileName = $tmpPath . 'installer_logo_' . rand() . '_file.svg';
$body['logo'] = str_replace('data:image/svg+xml;base64,', '', $body['logo']);
file_put_contents($tmpFileName, $file);
$size = strlen($file);
if ($size > 5000000) {
return $response->withStatus(400)->withJson(['errors' => 'Logo size is not allowed']);
}
copy($tmpFileName, "custom/{$body['customId']}/img/logo.svg");
}
DatabasePDO::reset();
new DatabasePDO(['customId' => $body['customId']]);
DatabaseModel::update([
'table' => 'parameters',
'set' => ['param_value_string' => $body['loginMessage'] ?? ''],
'where' => ['id = ?'],
'data' => ['loginpage_message']
]);
DatabaseModel::update([
'table' => 'parameters',
'set' => ['param_value_string' => $body['homeMessage'] ?? ''],
'where' => ['id = ?'],
'data' => ['homepage_message']
]);
return $response->withStatus(204);
}
public function updateAdministrator(Request $request, Response $response)
{
$body = $request->getParsedBody();
if (!Validator::stringType()->notEmpty()->validate($body['customId'])) {
return $response->withStatus(400)->withJson(['errors' => 'Body customId is empty or not a string']);
} elseif (!preg_match('/^[a-zA-Z0-9_\-]*$/', $body['customId'])) {
return $response->withStatus(400)->withJson(['errors' => 'Body customId has unauthorized characters']);
} elseif (!is_file("custom/{$body['customId']}/initializing.lck")) {
return $response->withStatus(403)->withJson(['errors' => 'Custom is already installed']);
} elseif (!Validator::stringType()->notEmpty()->validate($body['password'])) {
return $response->withStatus(400)->withJson(['errors' => 'Body password is empty or not a string']);
} elseif (!Validator::stringType()->notEmpty()->validate($body['email']) || !filter_var($body['email'], FILTER_VALIDATE_EMAIL)) {
return $response->withStatus(400)->withJson(['errors' => 'Body email is empty, not a string or not a valid email']);
}
DatabasePDO::reset();
new DatabasePDO(['customId' => $body['customId']]);
DatabaseModel::update([
'table' => 'users',
'set' => [
'password' => AuthenticationModel::getPasswordHash($body['password']),
'mail' => $body['email']
],
'where' => ['user_id = ?'],
'data' => ['superadmin']
]);
DatabaseModel::update([
'table' => 'users',
'set' => [
'mail' => $body['email']
],
'where' => ['mail = ?'],
return $response->withStatus(204);
}
public function terminateInstaller(Request $request, Response $response)
{
$body = $request->getParsedBody();
if (!Validator::stringType()->notEmpty()->validate($body['customId'])) {
return $response->withStatus(400)->withJson(['errors' => 'Body customId is empty or not a string']);
} elseif (!preg_match('/^[a-zA-Z0-9_\-]*$/', $body['customId'])) {
return $response->withStatus(400)->withJson(['errors' => 'Body customId has unauthorized characters']);
} elseif (!is_file("custom/{$body['customId']}/initializing.lck")) {
return $response->withStatus(403)->withJson(['errors' => 'Custom is already installed']);
}
unlink("custom/{$body['customId']}/initializing.lck");
$url = UrlController::getCoreUrl();
$url .= $body['customId'] . '/dist/index.html';
return $response->withJson(['url' => $url]);