Spaces:
No application file
No application file
| declare(strict_types=1); | |
| namespace Mautic\InstallBundle\Install; | |
| use Doctrine\Common\DataFixtures\Executor\ORMExecutor; | |
| use Doctrine\Common\DataFixtures\Purger\ORMPurger; | |
| use Doctrine\ORM\EntityManager; | |
| use Mautic\CoreBundle\Configurator\Configurator; | |
| use Mautic\CoreBundle\Configurator\Step\StepInterface; | |
| use Mautic\CoreBundle\Doctrine\Loader\FixturesLoaderInterface; | |
| use Mautic\CoreBundle\Helper\CacheHelper; | |
| use Mautic\CoreBundle\Helper\EncryptionHelper; | |
| use Mautic\CoreBundle\Helper\InputHelper; | |
| use Mautic\CoreBundle\Helper\PathsHelper; | |
| use Mautic\CoreBundle\Loader\ParameterLoader; | |
| use Mautic\CoreBundle\Release\ThisRelease; | |
| use Mautic\InstallBundle\Configurator\Step\DoctrineStep; | |
| use Mautic\InstallBundle\Exception\AlreadyInstalledException; | |
| use Mautic\InstallBundle\Exception\DatabaseVersionTooOldException; | |
| use Mautic\InstallBundle\Helper\SchemaHelper; | |
| use Mautic\UserBundle\Entity\Role; | |
| use Mautic\UserBundle\Entity\User; | |
| use Symfony\Bundle\FrameworkBundle\Console\Application; | |
| use Symfony\Component\Console\Input\ArgvInput; | |
| use Symfony\Component\Console\Output\BufferedOutput; | |
| use Symfony\Component\HttpKernel\KernelInterface; | |
| use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasher; | |
| use Symfony\Component\Validator\Constraints as Assert; | |
| use Symfony\Component\Validator\Validator\ValidatorInterface; | |
| use Symfony\Contracts\Translation\TranslatorInterface; | |
| class InstallService | |
| { | |
| public const CHECK_STEP = 0; | |
| public const DOCTRINE_STEP = 1; | |
| public const USER_STEP = 2; | |
| public const FINAL_STEP = 3; | |
| public function __construct( | |
| private Configurator $configurator, | |
| private CacheHelper $cacheHelper, | |
| protected PathsHelper $pathsHelper, | |
| private EntityManager $entityManager, | |
| private TranslatorInterface $translator, | |
| private KernelInterface $kernel, | |
| private ValidatorInterface $validator, | |
| private UserPasswordHasher $hasher, | |
| private FixturesLoaderInterface $fixturesLoader | |
| ) { | |
| } | |
| /** | |
| * Get step object for given index or appropriate step index. | |
| * | |
| * @param int $index The step number to retrieve | |
| * | |
| * @return StepInterface the valid step given installation status | |
| * | |
| * @throws \InvalidArgumentException|AlreadyInstalledException | |
| */ | |
| public function getStep(int $index = 0): StepInterface | |
| { | |
| // We're going to assume a bit here; if the config file exists already and DB info is provided, assume the app | |
| // is installed and redirect | |
| if ($this->checkIfInstalled()) { | |
| throw new AlreadyInstalledException(); | |
| } | |
| $params = $this->configurator->getParameters(); | |
| // Check to ensure the installer is in the right place | |
| if ((empty($params) | |
| || !isset($params['db_driver']) | |
| || empty($params['db_driver'])) && $index > 1) { | |
| return $this->configurator->getStep(self::DOCTRINE_STEP)[0]; | |
| } | |
| return $this->configurator->getStep($index)[0]; | |
| } | |
| /** | |
| * Get local config file location. | |
| */ | |
| private function localConfig(): string | |
| { | |
| return ParameterLoader::getLocalConfigFile($this->pathsHelper->getSystemPath('root').'/app'); | |
| } | |
| /** | |
| * Get local config parameters. | |
| */ | |
| public function localConfigParameters(): array | |
| { | |
| $localConfigFile = $this->localConfig(); | |
| if (file_exists($localConfigFile)) { | |
| /** @var array $parameters */ | |
| $parameters = []; | |
| // Load local config to override parameters | |
| include $localConfigFile; | |
| $localParameters = $parameters; | |
| } else { | |
| $localParameters = []; | |
| } | |
| return $localParameters; | |
| } | |
| /** | |
| * Checks if the application has been installed and redirects if so. | |
| */ | |
| public function checkIfInstalled(): bool | |
| { | |
| // If the config file doesn't even exist, no point in checking further | |
| $localConfigFile = $this->localConfig(); | |
| if (!file_exists($localConfigFile)) { | |
| return false; | |
| } | |
| $params = $this->configurator->getParameters(); | |
| // if db_driver and site_url are present then it is assumed all the steps of the installation have been | |
| // performed; manually deleting these values or deleting the config file will be required to re-enter | |
| // installation. | |
| if (empty($params['db_driver']) || empty($params['site_url'])) { | |
| return false; | |
| } | |
| return true; | |
| } | |
| /** | |
| * Translation messages array. | |
| */ | |
| private function translateMessages(array $messages): array | |
| { | |
| if (empty($messages)) { | |
| return $messages; | |
| } | |
| foreach ($messages as $key => $value) { | |
| $messages[$key] = $this->translator->trans($value); | |
| } | |
| return $messages; | |
| } | |
| /** | |
| * Checks for step's requirements. | |
| */ | |
| public function checkRequirements(StepInterface $step): array | |
| { | |
| $messages = $step->checkRequirements(); | |
| return $this->translateMessages($messages); | |
| } | |
| /** | |
| * Checks for step's optional settings. | |
| */ | |
| public function checkOptionalSettings(StepInterface $step): array | |
| { | |
| $messages = $step->checkOptionalSettings(); | |
| return $this->translateMessages($messages); | |
| } | |
| public function saveConfiguration($params, StepInterface $step = null, $clearCache = false): array | |
| { | |
| if ($step instanceof StepInterface) { | |
| $params = $step->update($step); | |
| } | |
| $this->configurator->mergeParameters($params); | |
| $messages = []; | |
| try { | |
| $this->configurator->write(); | |
| } catch (\RuntimeException) { | |
| $messages = [ | |
| 'error' => $this->translator->trans( | |
| 'mautic.installer.error.writing.configuration', | |
| [], | |
| 'flashes' | |
| ), | |
| ]; | |
| } | |
| if ($clearCache) { | |
| $this->cacheHelper->refreshConfig(); | |
| } | |
| return $messages; | |
| } | |
| /** | |
| * @return array Validation errors | |
| */ | |
| public function validateDatabaseParams(array $dbParams): array | |
| { | |
| $required = [ | |
| 'driver', | |
| 'host', | |
| 'name', | |
| 'user', | |
| ]; | |
| $messages = []; | |
| foreach ($required as $r) { | |
| if (!isset($dbParams[$r]) || empty($dbParams[$r])) { | |
| $messages[$r] = $this->translator->trans( | |
| 'mautic.core.value.required', | |
| [], | |
| 'validators' | |
| ); | |
| } | |
| } | |
| if (!isset($dbParams['port']) || (int) $dbParams['port'] <= 0) { | |
| $messages['port'] = $this->translator->trans( | |
| 'mautic.install.database.port.invalid', | |
| [], | |
| 'validators' | |
| ); | |
| } | |
| if (!empty($dbParams['driver']) && !in_array($dbParams['driver'], DoctrineStep::getDriverKeys())) { | |
| $messages['driver'] = $this->translator->trans( | |
| 'mautic.install.database.driver.invalid', | |
| ['%drivers%' => implode(', ', DoctrineStep::getDriverKeys())], | |
| 'validators' | |
| ); | |
| } | |
| return $messages; | |
| } | |
| /** | |
| * Create the database. | |
| */ | |
| public function createDatabaseStep(StepInterface $step, array $dbParams): array | |
| { | |
| $messages = $this->validateDatabaseParams($dbParams); | |
| if (!empty($messages)) { | |
| return $messages; | |
| } | |
| // Check if connection works and/or create database if applicable | |
| $schemaHelper = new SchemaHelper($dbParams); | |
| try { | |
| $schemaHelper->testConnection(); | |
| $schemaHelper->validateDatabaseVersion(); | |
| if ($schemaHelper->createDatabase()) { | |
| $messages = $this->saveConfiguration($dbParams, $step, true); | |
| if (empty($messages)) { | |
| return $messages; | |
| } | |
| } | |
| $messages['error'] = $this->translator->trans( | |
| 'mautic.installer.error.creating.database', | |
| ['%name%' => $dbParams['name']], | |
| 'flashes' | |
| ); | |
| } catch (DatabaseVersionTooOldException $e) { | |
| $metadata = ThisRelease::getMetadata(); | |
| $messages['error'] = $this->translator->trans( | |
| 'mautic.installer.error.database.version', | |
| [ | |
| '%currentversion%' => $e->getCurrentVersion(), | |
| '%mysqlminversion%' => $metadata->getMinSupportedMySqlVersion(), | |
| '%mariadbminversion%' => $metadata->getMinSupportedMariaDbVersion(), | |
| ], | |
| 'flashes' | |
| ); | |
| } catch (\Exception $exception) { | |
| $messages['error'] = $this->translator->trans( | |
| 'mautic.installer.error.connecting.database', | |
| ['%exception%' => $exception->getMessage()], | |
| 'flashes' | |
| ); | |
| } | |
| return $messages; | |
| } | |
| /** | |
| * Create the database schema. | |
| */ | |
| public function createSchemaStep(array $dbParams): array | |
| { | |
| $schemaHelper = new SchemaHelper($dbParams); | |
| $schemaHelper->setEntityManager($this->entityManager); | |
| $messages = []; | |
| try { | |
| if (!$schemaHelper->installSchema()) { | |
| $messages['error'] = $this->translator->trans( | |
| 'mautic.installer.error.no.metadata', | |
| [], | |
| 'flashes' | |
| ); | |
| } | |
| } catch (\Exception $exception) { | |
| $messages['error'] = $this->translator->trans( | |
| 'mautic.installer.error.installing.data', | |
| ['%exception%' => $exception->getMessage()], | |
| 'flashes' | |
| ); | |
| } | |
| return $messages; | |
| } | |
| /** | |
| * Load the database fixtures in the database. | |
| */ | |
| public function createFixturesStep(): array | |
| { | |
| $messages = []; | |
| try { | |
| $this->installDatabaseFixtures(); | |
| } catch (\Exception $exception) { | |
| $messages['error'] = $this->translator->trans( | |
| 'mautic.installer.error.adding.fixtures', | |
| ['%exception%' => $exception->getMessage()], | |
| 'flashes' | |
| ); | |
| } | |
| return $messages; | |
| } | |
| /** | |
| * Installs data fixtures for the application. | |
| * | |
| * @throws \InvalidArgumentException | |
| */ | |
| public function installDatabaseFixtures(): void | |
| { | |
| $fixtures = $this->fixturesLoader->getFixtures(['group_install']); | |
| if (!$fixtures) { | |
| throw new \InvalidArgumentException('Could not find any fixtures to load with the "group_install" group.'); | |
| } | |
| $purger = new ORMPurger($this->entityManager); | |
| $purger->setPurgeMode(ORMPurger::PURGE_MODE_DELETE); | |
| $executor = new ORMExecutor($this->entityManager, $purger); | |
| /* | |
| * FIXME entity manager does not load configuration if local.php just created by CLI install | |
| * [error] An error occurred while attempting to add default data | |
| * An exception occured in driver: | |
| * SQLSTATE[HY000] [1045] Access refused for user: ''@'@localhost' (mot de passe: NON) | |
| */ | |
| $executor->execute($fixtures, true); | |
| } | |
| /** | |
| * Create the administrator user. | |
| */ | |
| public function createAdminUserStep(array $data): array | |
| { | |
| $entityManager = $this->entityManager; | |
| // ensure the username and email are unique | |
| try { | |
| /** @var User $existingUser */ | |
| $existingUser = $entityManager->getRepository(User::class)->find(1); | |
| } catch (\Exception) { | |
| $existingUser = null; | |
| } | |
| if (null !== $existingUser) { | |
| $user = $existingUser; | |
| } else { | |
| $user = new User(); | |
| } | |
| $required = [ | |
| 'firstname', | |
| 'lastname', | |
| 'username', | |
| 'email', | |
| 'password', | |
| ]; | |
| $messages = []; | |
| foreach ($required as $r) { | |
| if (!isset($data[$r])) { | |
| $messages[$r] = $this->translator->trans( | |
| 'mautic.core.value.required', | |
| [], | |
| 'validators' | |
| ); | |
| } | |
| } | |
| if (!empty($messages)) { | |
| return $messages; | |
| } | |
| $validations = []; | |
| $emailConstraint = new Assert\Email(); | |
| $emailConstraint->message = $this->translator->trans('mautic.core.email.required', [], 'validators'); | |
| $passwordConstraint = new Assert\Length(['min' => 6]); | |
| $passwordConstraint->minMessage = $this->translator->trans('mautic.install.password.minlength', [], 'validators'); | |
| $validations[] = $this->validator->validate($data['email'], $emailConstraint); | |
| $validations[] = $this->validator->validate($data['password'], $passwordConstraint); | |
| $messages = []; | |
| foreach ($validations as $errors) { | |
| foreach ($errors as $error) { | |
| $messages[] = $error->getMessage(); | |
| } | |
| } | |
| if (!empty($messages)) { | |
| return $messages; | |
| } | |
| $hasher = $this->hasher; | |
| $user->setFirstName(InputHelper::clean($data['firstname'])); | |
| $user->setLastName(InputHelper::clean($data['lastname'])); | |
| $user->setUsername(InputHelper::clean($data['username'])); | |
| $user->setEmail(InputHelper::email($data['email'])); | |
| $user->setPassword($hasher->hashPassword($user, $data['password'])); | |
| $adminRole = null; | |
| try { | |
| $adminRole = $entityManager->getReference(Role::class, 1); | |
| } catch (\Exception $exception) { | |
| $messages['error'] = $this->translator->trans( | |
| 'mautic.installer.error.getting.role', | |
| ['%exception%' => $exception->getMessage()], | |
| 'flashes' | |
| ); | |
| } | |
| if (!empty($adminRole)) { | |
| $user->setRole($adminRole); | |
| try { | |
| $entityManager->persist($user); | |
| $entityManager->flush(); | |
| } catch (\Exception $exception) { | |
| $messages['error'] = $this->translator->trans( | |
| 'mautic.installer.error.creating.user', | |
| ['%exception%' => $exception->getMessage()], | |
| 'flashes' | |
| ); | |
| } | |
| } | |
| return $messages; | |
| } | |
| /** | |
| * Create the final configuration. | |
| */ | |
| public function createFinalConfigStep(string $siteUrl): array | |
| { | |
| // Merge final things into the config, wipe the container, and we're done! | |
| $finalConfigVars = [ | |
| 'secret_key' => EncryptionHelper::generateKey(), | |
| 'site_url' => $siteUrl, | |
| ]; | |
| return $this->saveConfiguration($finalConfigVars, null, true); | |
| } | |
| /** | |
| * Final migration step for install. | |
| */ | |
| public function finalMigrationStep(): void | |
| { | |
| // Add database migrations up to this point since this is a fresh install (must be done at this point | |
| // after the cache has been rebuilt | |
| $input = new ArgvInput(['console', 'doctrine:migrations:version', '--add', '--all', '--no-interaction']); | |
| $output = new BufferedOutput(); | |
| $application = new Application($this->kernel); | |
| $application->setAutoExit(false); | |
| $application->run($input, $output); | |
| } | |
| } | |