jwht-hrm-5.4/installer/Command/InstallOnNewDatabaseCommand.php

435 lines
18 KiB
PHP

<?php
/**
* OrangeHRM is a comprehensive Human Resource Management (HRM) System that captures
* all the essential functionalities required for any enterprise.
* Copyright (C) 2006 OrangeHRM Inc., http://www.orangehrm.com
*
* OrangeHRM is free software; you can redistribute it and/or modify it under the terms of
* the GNU General Public License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* OrangeHRM is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA
*/
namespace OrangeHRM\Installer\Command;
use DateTimeZone;
use OrangeHRM\Authentication\Dto\UserCredential;
use OrangeHRM\Config\Config;
use OrangeHRM\Framework\Http\Request;
use OrangeHRM\Installer\Controller\Installer\Api\ConfigFileAPI;
use OrangeHRM\Installer\Controller\Installer\Api\InstallerDataRegistrationAPI;
use OrangeHRM\Installer\Exception\InterruptProcessException;
use OrangeHRM\Installer\Exception\InvalidArgumentException;
use OrangeHRM\Installer\Framework\InstallerCommand;
use OrangeHRM\Installer\Util\AppSetupUtility;
use OrangeHRM\Installer\Util\Connection;
use OrangeHRM\Installer\Util\DatabaseUserPermissionEvaluator;
use OrangeHRM\Installer\Util\InstanceCreationHelper;
use OrangeHRM\Installer\Util\Logger;
use OrangeHRM\Installer\Util\StateContainer;
use OrangeHRM\Installer\Util\SystemCheck;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ChoiceQuestion;
use Symfony\Component\Console\Question\Question;
use Throwable;
class InstallOnNewDatabaseCommand extends InstallerCommand
{
use InstallerCommandHelperTrait;
public const REQUIRED_TAG = '<comment>(required)</comment>';
public const REQUIRED_WARNING = 'This field cannot be empty';
public const STEP_1 = '创建数据库';
public const STEP_2 = '检查数据库权限';
public const STEP_3 = '更新最新数据';
public const STEP_4 = '实例化管理员账号';
public const STEP_5 = '新建HRM用户';
public const STEP_6 = '设置配置文件';
private InputInterface $input;
private OutputInterface $output;
private AppSetupUtility $appSetupUtility;
/**
* @inheritDoc
*/
public function getCommandName(): string
{
return 'install:on-new-database';
}
/**
* @inheritDoc
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
if (!$input->isInteractive()) {
$this->getIO()->error('Not supported non interactive mode.');
return self::INVALID;
}
if (Config::isInstalled()) {
$this->getIO()->error('This system already installed.');
return self::FAILURE;
}
$this->input = $input;
$this->output = $output;
try {
return $this->executeCommand($input, $output);
} catch (InterruptProcessException $e) {
return self::FAILURE;
}
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @return int
*/
protected function executeCommand(InputInterface $input, OutputInterface $output): int
{
$this->licenseAcceptance();
$this->databaseInformation();
$this->systemCheck();
$this->instanceCreation();
$this->adminUserCreation();
$this->installation();
return self::SUCCESS;
}
/**
* @return InputInterface
*/
protected function getInput(): InputInterface
{
return $this->input;
}
/**
* @return OutputInterface
*/
protected function getOutput(): OutputInterface
{
return $this->output;
}
/**
* @return AppSetupUtility
*/
public function getAppSetupUtility(): AppSetupUtility
{
return $this->appSetupUtility ??= new AppSetupUtility();
}
protected function licenseAcceptance(): void
{
$this->getIO()->title('License Acceptance');
$this->getIO()->block('Please review the license terms before installing OrangeHRM Starter.');
$this->getIO()->block('You can find the license file ("LICENSE") at the root folder of the code.');
if ($this->getIO()->confirm('I accept the terms in the License Agreement') !== true) {
throw new InterruptProcessException();
}
}
protected function databaseInformation(): void
{
dbInfo:
$this->getIO()->title('Database Configuration');
$this->getIO()->block('Please enter your database configuration information below.');
$dbHost = $this->getRequiredField('Database Host Name');
$dbPort = $this->getIO()->ask(
'Database Host Port',
3306,
fn (?string $value) => $this->databasePortValidator($value)
);
$dbName = $this->getRequiredField('Database Name', function ($value) {
$value = $this->validateStrLength($value, 64);
return $this->alphanumericValidator($value, 'Database name should not contain special characters');
});
$this->getIO()->writeln(
"<comment>Privileged Database User:</comment>\nShould have the rights to create databases, create tables, insert data into table, alter table structure and to create database users."
);
$this->getIO()->writeln(
"<comment>OrangeHRM Database User:</comment>\nShould have the rights to insert data into table, update data in a table, delete data in a table."
);
$dbUser = $this->getRequiredField('Privileged Database Username');
$dbPassword = $this->getIO()->askHidden('Privileged Database User Password <comment>(hidden)</comment>');
$useSameDbUser = $this->getIO()->confirm(
'Use the same `Privileged Database User` as `OrangeHRM Database User`',
false
);
$ohrmDbUser = $dbUser;
$ohrmDbPassword = $dbPassword;
if ($useSameDbUser === false) {
$ohrmDbUser = $this->getRequiredField('OrangeHRM Database Username');
$ohrmDbPassword = $this->getIO()->askHidden(
'OrangeHRM Database User Password <comment>(hidden)</comment>'
);
}
$enableDataEncryption = $this->getIO()->confirm('Enable Data Encryption', false);
StateContainer::getInstance()->storeDbInfo(
$dbHost,
$dbPort,
new UserCredential($dbUser, $dbPassword),
$dbName,
new UserCredential($ohrmDbUser, $ohrmDbPassword),
$enableDataEncryption
);
StateContainer::getInstance()->setDbType(AppSetupUtility::INSTALLATION_DB_TYPE_NEW);
$connection = $this->getAppSetupUtility()->connectToDatabaseServer();
if ($connection->hasError()) {
$this->getIO()->error($connection->getErrorMessage());
StateContainer::getInstance()->clearDbInfo();
goto dbInfo;
}
if ($this->getAppSetupUtility()->isDatabaseExist($dbName)) {
$this->getIO()->error('Database Already Exist');
StateContainer::getInstance()->clearDbInfo();
goto dbInfo;
}
if (!$useSameDbUser && $this->getAppSetupUtility()->isDatabaseUserExist($ohrmDbUser)) {
$this->getIO()->error(
"Database User `$ohrmDbUser` Already Exist. Please Use Another Username for `OrangeHRM Database Username`."
);
StateContainer::getInstance()->clearDbInfo();
goto dbInfo;
}
}
protected function systemCheck(): void
{
$systemCheck = new SystemCheck();
$results = $systemCheck->getSystemCheckResults(true);
$dbInfo = StateContainer::getInstance()->getDbInfo();
if (isset($dbInfo[StateContainer::ENABLE_DATA_ENCRYPTION])
&& $dbInfo[StateContainer::ENABLE_DATA_ENCRYPTION] == true) {
$results[1]['checks'][] = [
'label' => 'Write Permissions for “lib/confs/cryptokeys”',
'value' => $systemCheck->isWritableCryptoKeyDir()
];
}
$this->drawSystemCheckTable($results);
if ($systemCheck->isInterruptContinue()) {
$this->getIO()->error('System check failed');
$systemCheckAcceptRisk = $this->getIO()->confirm('Do you want to accept the risk and continue?', false);
if ($systemCheckAcceptRisk !== true) {
throw new InterruptProcessException();
}
$this->getIO()->warning('Accepted the risk, so continue the upgrader.');
}
}
protected function instanceCreation(): void
{
$this->getIO()->title('Instance Creation');
$this->getIO()->block(
'Fill in your organization details here. Details entered in this section will be captured to create your OrangeHRM Instance'
);
$organizationName = $this->getRequiredField(
'Organization Name',
fn ($value) => $this->validateStrLength($value, 100)
);
$countries = array_combine(
array_column(InstanceCreationHelper::COUNTRIES, 'id'),
array_column(InstanceCreationHelper::COUNTRIES, 'label')
);
asort($countries);
$countries = array_map(fn ($country) => strtolower($country), $countries);
$countries = array_flip($countries);
$countryQuestion = new Question('Country ' . self::REQUIRED_TAG);
$countryQuestion->setValidator(
static function (?string $country) use ($countries): string {
if (!isset($countries[$country])) {
throw new InvalidArgumentException('Invalid option');
}
return $country;
}
);
$this->getIO()->block("Tip:\n* Use lowercase letters\n* Use `Tab` to autocomplete");
$countryQuestion->setAutocompleterValues(array_keys($countries));
$country = $this->getIO()->askQuestion($countryQuestion);
$countryCode = $countries[$country];
$this->getIO()->block("Tip:\n* Use correct letter case\n* Use `Tab` to autocomplete");
$languages = array_combine(
array_column(InstanceCreationHelper::LANGUAGES, 'id'),
array_column(InstanceCreationHelper::LANGUAGES, 'label')
);
$languageQuestion = new ChoiceQuestion('Language', $languages);
$languageQuestion->setValidator(
static function (?string $language) use ($languages): ?string {
if ($language == null || trim($language) === '') {
return $language;
}
if (!(isset($languages[$language]) || in_array($language, array_values($languages)))) {
throw new InvalidArgumentException('Invalid option');
}
return $language;
}
);
$langCode = $this->getIO()->askQuestion($languageQuestion);
$timeZoneGroups = array_combine(
array_column(InstanceCreationHelper::TIME_ZONE_GROUPS, 'label'),
array_column(InstanceCreationHelper::TIME_ZONE_GROUPS, 'id')
);
$timezoneGroupQuestion = new ChoiceQuestion('Timezone Group', array_keys($timeZoneGroups));
$timezoneGroupQuestion->setValidator(
static function (?string $timezoneGroup) use ($timeZoneGroups): ?string {
if ($timezoneGroup == null || trim($timezoneGroup) === '') {
return $timezoneGroup;
}
if (!isset($timeZoneGroups[$timezoneGroup])) {
throw new InvalidArgumentException('Invalid option');
}
return $timezoneGroup;
}
);
$timeZoneGroupName = $this->getIO()->askQuestion($timezoneGroupQuestion);
$timezones = DateTimeZone::listIdentifiers($timeZoneGroups[$timeZoneGroupName] ?? DateTimeZone::ALL);
$timezoneQuestion = new ChoiceQuestion('Timezone', $timezones);
$timezoneQuestion->setValidator(
static function (?string $timezone) use ($timezones): ?string {
if ($timezone == null || trim($timezone) === '') {
return $timezone;
}
if (!in_array($timezone, $timezones)) {
throw new InvalidArgumentException('Invalid option');
}
return $timezone;
}
);
$timezone = $this->getIO()->askQuestion($timezoneQuestion);
StateContainer::getInstance()->storeInstanceData($organizationName, $countryCode, $langCode, $timezone);
}
protected function adminUserCreation(): void
{
$firstName = $this->getRequiredField('Employee First Name', fn ($value) => $this->validateStrLength($value, 30));
$lastName = $this->getRequiredField('Employee Last Name', fn ($value) => $this->validateStrLength($value, 30));
$email = $this->getRequiredField('Email', function ($value) {
$value = $this->validateStrLength($value, 50);
return $this->emailValidator($value, 'Expected format: admin@example.com');
});
$contact = $this->getIO()->ask('Contact Number', null, function ($value) {
if ($value === null) {
return null;
}
$value = $this->validateStrLength($value, 25);
return $this->phoneNumberValidator($value, 'Allows numbers and only + - / ( )');
});
$username = $this->getRequiredField('Admin Username', fn ($value) => $this->validateStrLength($value, 40));
$password = $this->getIO()->askHidden('Password <comment>(hidden)</comment>', function ($value) {
$value = $this->requiredValidator($value);
return $this->validateStrLength($value, 64);
});
$this->getIO()->askHidden(
'Confirm Password <comment>(hidden)</comment>',
function ($value) use ($password) {
$value = $this->requiredValidator($value);
$value = $this->validateStrLength($value, 64);
if ($value !== $password) {
throw new InvalidArgumentException('Passwords do not match');
}
return $value;
}
);
StateContainer::getInstance()->storeAdminUserData(
$firstName,
$lastName,
$email,
new UserCredential($username, $password),
$contact
);
$regConsent = $this->getIO()->confirm(
'Register your system with OrangeHRM. By registering, You will be eligible for free support via emails, receive security alerts and news letters from OrangeHRM.',
true
);
StateContainer::getInstance()->storeRegConsent($regConsent);
$this->getIO()->note(
'Users who seek access to their data, or who seek to correct, amend, or delete the given information should direct their requests to data@orangehrm.com'
);
}
protected function installation(): void
{
$continue = $this->getIO()->confirm('Do you want to start the installer?', true);
if ($continue !== true) {
$this->getIO()->info('Aborted');
throw new InterruptProcessException();
}
$step1 = $this->startSection($this->getOutput(), self::STEP_1);
$step2 = $this->startSection($this->getOutput(), self::STEP_2);
$step3 = $this->startSection($this->getOutput(), self::STEP_3);
$step4 = $this->startSection($this->getOutput(), self::STEP_4);
$step5 = $this->startSection($this->getOutput(), self::STEP_5);
$step6 = $this->startSection($this->getOutput(), self::STEP_6, "\n");
$this->startStep($step1, self::STEP_1);
$request = new Request();
$request->setMethod(Request::METHOD_POST);
(new InstallerDataRegistrationAPI())->handle($request);
$appSetupUtility = $this->getAppSetupUtility();
$appSetupUtility->createDatabase();
$this->completeStep($step1, self::STEP_1);
$this->startStep($step2, self::STEP_2);
try {
$evaluator = new DatabaseUserPermissionEvaluator(Connection::getConnection());
$evaluator->evalPrivilegeDatabaseUserPermission();
} catch (Throwable $e) {
Logger::getLogger()->error($e->getMessage());
Logger::getLogger()->error($e->getTraceAsString());
$this->getIO()->error(
'`Checking database permissions` failed. For more details check the error log in /src/log/installer.log file'
);
throw new InterruptProcessException();
}
$this->completeStep($step2, self::STEP_2);
$this->startStep($step3, self::STEP_3);
$appSetupUtility->runMigrations('3.3.3', Config::PRODUCT_VERSION);
$this->completeStep($step3, self::STEP_3);
$this->startStep($step4, self::STEP_4);
$appSetupUtility->insertSystemConfiguration();
$this->completeStep($step4, self::STEP_4);
$this->startStep($step5, self::STEP_5);
$appSetupUtility->createDBUser();
$this->completeStep($step5, self::STEP_5);
$this->startStep($step6, self::STEP_6, "\n");
$request = new Request();
$request->setMethod(Request::METHOD_POST);
(new ConfigFileAPI())->handle($request);
$this->completeStep($step6, self::STEP_6, "\n");
}
}