Issues (3627)

bundles/InstallBundle/Install/InstallService.php (2 issues)

1
<?php
2
3
/*
4
 * @copyright   2019 Mautic Contributors. All rights reserved
5
 * @author      Mautic
6
 *
7
 * @link        http://mautic.org
8
 *
9
 * @license     GNU/GPLv3 http://www.gnu.org/licenses/gpl-3.0.html
10
 */
11
12
namespace Mautic\InstallBundle\Install;
13
14
use Doctrine\Common\DataFixtures\Executor\ORMExecutor;
15
use Doctrine\Common\DataFixtures\Purger\ORMPurger;
16
use Doctrine\ORM\EntityManager;
17
use Mautic\CoreBundle\Configurator\Configurator;
18
use Mautic\CoreBundle\Configurator\Step\StepInterface;
19
use Mautic\CoreBundle\Helper\CacheHelper;
20
use Mautic\CoreBundle\Helper\EncryptionHelper;
21
use Mautic\CoreBundle\Helper\PathsHelper;
22
use Mautic\InstallBundle\Helper\SchemaHelper;
23
use Mautic\UserBundle\Entity\User;
24
use Symfony\Bridge\Doctrine\DataFixtures\ContainerAwareLoader;
25
use Symfony\Bundle\FrameworkBundle\Console\Application;
26
use Symfony\Component\Console\Input\ArgvInput;
27
use Symfony\Component\Console\Output\BufferedOutput;
28
use Symfony\Component\DependencyInjection\ContainerInterface;
29
use Symfony\Component\HttpKernel\KernelInterface;
30
use Symfony\Component\Security\Core\Encoder\EncoderFactory;
31
use Symfony\Component\Translation\TranslatorInterface;
32
use Symfony\Component\Validator\Constraints as Assert;
33
use Symfony\Component\Validator\Validator\ValidatorInterface;
34
35
/**
36
 * Class InstallService.
37
 */
38
class InstallService
39
{
40
    const CHECK_STEP    = 0;
41
    const DOCTRINE_STEP = 1;
42
    const USER_STEP     = 2;
43
    const EMAIL_STEP    = 3;
44
    const FINAL_STEP    = 4;
45
46
    private $configurator;
47
48
    private $cacheHelper;
49
50
    protected $pathsHelper;
51
52
    private $entityManager;
53
54
    private $translator;
55
56
    private $kernel;
57
58
    private $validator;
59
60
    private $encoder;
61
62
    /**
63
     * InstallService constructor.
64
     */
65
    public function __construct(Configurator $configurator,
66
                                CacheHelper $cacheHelper,
67
                                PathsHelper $pathsHelper,
68
                                EntityManager $entityManager,
69
                                TranslatorInterface $translator,
70
                                KernelInterface $kernel,
71
                                ValidatorInterface $validator,
72
                                EncoderFactory $encoder)
73
    {
74
        $this->configurator             = $configurator;
75
        $this->cacheHelper              = $cacheHelper;
76
        $this->pathsHelper              = $pathsHelper;
77
        $this->entityManager            = $entityManager;
78
        $this->translator               = $translator;
79
        $this->kernel                   = $kernel;
80
        $this->validator                = $validator;
81
        $this->encoder                  = $encoder;
82
    }
83
84
    /**
85
     * Get step object for given index or appropriate step index.
86
     *
87
     * @param int $index The step number to retrieve
88
     *
89
     * @return bool|StepInterface the valid step given installation status
90
     *
91
     * @throws \InvalidArgumentException
92
     */
93
    public function getStep($index = 0)
94
    {
95
        // We're going to assume a bit here; if the config file exists already and DB info is provided, assume the app
96
        // is installed and redirect
97
        if ($this->checkIfInstalled()) {
98
            return true;
99
        }
100
101
        if (false !== ($pos = strpos($index, '.'))) {
102
            $index = (int) substr($index, 0, $pos);
103
        }
104
105
        $params = $this->configurator->getParameters();
106
107
        // Check to ensure the installer is in the right place
108
        if ((empty($params)
109
                || !isset($params['db_driver'])
110
                || empty($params['db_driver'])) && $index > 1) {
111
            return $this->configurator->getStep(self::DOCTRINE_STEP);
112
        }
113
114
        return $this->configurator->getStep($index)[0];
115
    }
116
117
    /**
118
     * Get local config file location.
119
     *
120
     * @return string
121
     */
122
    private function localConfig()
123
    {
124
        return $this->pathsHelper->getSystemPath('local_config', false);
125
    }
126
127
    /**
128
     * Get local config parameters.
129
     *
130
     * @return array
131
     */
132
    public function localConfigParameters()
133
    {
134
        $localConfigFile = $this->localConfig();
135
136
        if (file_exists($localConfigFile)) {
137
            /** @var array $parameters */
138
            $parameters = false;
139
140
            // Load local config to override parameters
141
            include $localConfigFile;
142
            $localParameters = (is_array($parameters)) ? $parameters : [];
0 ignored issues
show
The condition is_array($parameters) is always true.
Loading history...
143
        } else {
144
            $localParameters = [];
145
        }
146
147
        return $localParameters;
148
    }
149
150
    /**
151
     * Checks if the application has been installed and redirects if so.
152
     *
153
     * @return bool
154
     */
155
    public function checkIfInstalled()
156
    {
157
        // If the config file doesn't even exist, no point in checking further
158
        $localConfigFile = $this->localConfig();
159
        if (!file_exists($localConfigFile)) {
160
            return false;
161
        }
162
163
        /** @var \Mautic\CoreBundle\Configurator\Configurator $configurator */
164
        $params = $this->configurator->getParameters();
165
166
        // if db_driver and mailer_from_name are present then it is assumed all the steps of the installation have been
167
        // performed; manually deleting these values or deleting the config file will be required to re-enter
168
        // installation.
169
        if (empty($params['db_driver']) || empty($params['mailer_from_name'])) {
170
            return false;
171
        }
172
173
        return true;
174
    }
175
176
    /**
177
     * Translation messages array.
178
     *
179
     * @param array $messages
180
     *
181
     * @return array
182
     */
183
    private function translateMessage($messages)
184
    {
185
        $translator = $this->translator;
186
187
        if (is_array($messages) && !empty($messages)) {
188
            foreach ($messages as $key => $value) {
189
                $messages[$key] = $translator->trans($value);
190
            }
191
        }
192
193
        return $messages;
194
    }
195
196
    /**
197
     * Checks for step's requirements.
198
     *
199
     * @param StepInterface|null $step
200
     *
201
     * @return array
202
     */
203
    public function checkRequirements($step)
204
    {
205
        $messages = $step->checkRequirements();
206
207
        return $this->translateMessage($messages);
208
    }
209
210
    /**
211
     * Checks for step's optional settings.
212
     *
213
     * @param StepInterface|null $step
214
     *
215
     * @return array
216
     */
217
    public function checkOptionalSettings($step)
218
    {
219
        $messages = $step->checkOptionalSettings();
220
221
        return $this->translateMessage($messages);
222
    }
223
224
    /**
225
     * @param array|StepInterface $params
226
     * @param StepInterface|null  $step
227
     * @param bool                $clearCache
228
     *
229
     * @return bool
230
     */
231
    public function saveConfiguration($params, $step = null, $clearCache = false)
232
    {
233
        $translator = $this->translator;
234
235
        if (null !== $step && $step instanceof StepInterface) {
236
            $params = $step->update($step);
237
        }
238
239
        $this->configurator->mergeParameters($params);
240
241
        $messages = false;
242
243
        try {
244
            $this->configurator->write();
245
            $messages = true;
246
        } catch (\RuntimeException $exception) {
247
            $messages          = [];
248
            $messages['error'] = $translator->trans(
249
                'mautic.installer.error.writing.configuration',
250
                [],
251
                'flashes');
252
        }
253
254
        if ($clearCache) {
255
            $this->cacheHelper->refreshConfig();
256
        }
257
258
        return $messages;
259
    }
260
261
    /**
262
     * @param array $dbParams
263
     *
264
     * @return array|bool
265
     */
266
    public function validateDatabaseParams($dbParams)
267
    {
268
        $translator = $this->translator;
269
270
        $required = [
271
            'driver',
272
            'host',
273
            'name',
274
            'user',
275
        ];
276
277
        $messages = [];
278
        foreach ($required as $r) {
279
            if (!isset($dbParams[$r]) || empty($dbParams[$r])) {
280
                $messages[$r] = $translator->trans(
281
                    'mautic.core.value.required',
282
                    [],
283
                    'validators'
284
                );
285
            }
286
        }
287
288
        if (!isset($dbParams['port']) || (int) $dbParams['port'] <= 0) {
289
            $messages['port'] = $translator->trans(
290
                'mautic.install.database.port.invalid',
291
                [],
292
                'validators'
293
            );
294
        }
295
296
        return empty($messages) ? true : $messages;
297
    }
298
299
    /**
300
     * Create the database.
301
     *
302
     * @param StepInterface|null $step
303
     * @param array              $dbParams
304
     *
305
     * @return array|bool
306
     */
307
    public function createDatabaseStep($step, $dbParams)
308
    {
309
        $translator = $this->translator;
310
311
        $messages = $this->validateDatabaseParams($dbParams);
312
313
        if (is_bool($messages) && true === $messages) {
0 ignored issues
show
The condition is_bool($messages) is always false.
Loading history...
314
            // Check if connection works and/or create database if applicable
315
            $schemaHelper = new SchemaHelper($dbParams);
316
317
            try {
318
                $schemaHelper->testConnection();
319
320
                if ($schemaHelper->createDatabase()) {
321
                    $messages = $this->saveConfiguration($dbParams, $step, true);
322
                    if (is_bool($messages)) {
323
                        return $messages;
324
                    }
325
326
                    $messages['error'] = $translator->trans(
327
                        'mautic.installer.error.writing.configuration',
328
                        [],
329
                        'flashes');
330
                } else {
331
                    $messages['error'] = $translator->trans(
332
                        'mautic.installer.error.creating.database',
333
                        ['%name%' => $dbParams['name']],
334
                        'flashes');
335
                }
336
            } catch (\Exception $exception) {
337
                $messages['error'] = $translator->trans(
338
                    'mautic.installer.error.connecting.database',
339
                    ['%exception%' => $exception->getMessage()],
340
                    'flashes');
341
            }
342
        }
343
344
        return $messages;
345
    }
346
347
    /**
348
     * Create the database schema.
349
     *
350
     * @param array $dbParams
351
     *
352
     * @return array|bool
353
     */
354
    public function createSchemaStep($dbParams)
355
    {
356
        $translator    = $this->translator;
357
        $entityManager = $this->entityManager;
358
        $schemaHelper  = new SchemaHelper($dbParams);
359
        $schemaHelper->setEntityManager($entityManager);
360
361
        $messages = [];
362
        try {
363
            if (!$schemaHelper->installSchema()) {
364
                $messages['error'] = $translator->trans(
365
                    'mautic.installer.error.no.metadata',
366
                    [],
367
                    'flashes');
368
            } else {
369
                $messages = true;
370
            }
371
        } catch (\Exception $exception) {
372
            $messages['error'] = $translator->trans(
373
                'mautic.installer.error.installing.data',
374
                ['%exception%' => $exception->getMessage()],
375
                'flashes');
376
        }
377
378
        return $messages;
379
    }
380
381
    /**
382
     * Load the database fixtures in the database.
383
     *
384
     * @return array|bool
385
     */
386
    public function createFixturesStep(ContainerInterface $container)
387
    {
388
        $translator = $this->translator;
389
390
        $messages = [];
391
        try {
392
            $this->installDatabaseFixtures($container);
393
            $messages = true;
394
        } catch (\Exception $exception) {
395
            $messages['error'] = $translator->trans(
396
                'mautic.installer.error.adding.fixtures',
397
                ['%exception%' => $exception->getMessage()],
398
                'flashes');
399
        }
400
401
        return $messages;
402
    }
403
404
    /**
405
     * Installs data fixtures for the application.
406
     *
407
     * @return bool boolean true on success
408
     */
409
    public function installDatabaseFixtures(ContainerInterface $container)
410
    {
411
        $entityManager = $this->entityManager;
412
        $paths         = [dirname(__DIR__).'/InstallFixtures/ORM'];
413
        $loader        = new ContainerAwareLoader($container);
414
415
        foreach ($paths as $path) {
416
            if (is_dir($path)) {
417
                $loader->loadFromDirectory($path);
418
            }
419
        }
420
421
        $fixtures = $loader->getFixtures();
422
423
        if (!$fixtures) {
424
            throw new \InvalidArgumentException(sprintf('Could not find any fixtures to load in: %s', "\n\n- ".implode("\n- ", $paths)));
425
        }
426
427
        $purger = new ORMPurger($entityManager);
428
        $purger->setPurgeMode(ORMPurger::PURGE_MODE_DELETE);
429
        $executor = new ORMExecutor($entityManager, $purger);
430
        /*
431
         * FIXME entity manager does not load configuration if local.php just created by CLI install
432
         * [error] An error occurred while attempting to add default data
433
         * An exception occured in driver:
434
         * SQLSTATE[HY000] [1045] Access refused for user: ''@'@localhost' (mot de passe: NON)
435
         */
436
        $executor->execute($fixtures, true);
437
438
        return true;
439
    }
440
441
    /**
442
     * Create the administrator user.
443
     *
444
     * @param array $data
445
     *
446
     * @return array|bool
447
     *
448
     * @throws ORMException
449
     * @throws OptimisticLockException
450
     */
451
    public function createAdminUserStep($data)
452
    {
453
        $entityManager = $this->entityManager;
454
455
        //ensure the username and email are unique
456
        try {
457
            $existingUser = $entityManager->getRepository('MauticUserBundle:User')->find(1);
458
        } catch (\Exception $e) {
459
            $existingUser = null;
460
        }
461
462
        if (null != $existingUser) {
463
            $user = $existingUser;
464
        } else {
465
            $user = new User();
466
        }
467
468
        $translator = $this->translator;
469
470
        $required = [
471
            'firstname',
472
            'lastname',
473
            'username',
474
            'email',
475
            'password',
476
        ];
477
478
        $messages = [];
479
        foreach ($required as $r) {
480
            if (!isset($data[$r])) {
481
                $messages[$r] = $translator->trans(
482
                    'mautic.core.value.required',
483
                    [],
484
                    'validators'
485
                );
486
            }
487
        }
488
489
        if (!empty($messages)) {
490
            return $messages;
491
        }
492
493
        $emailConstraint          = new Assert\Email();
494
        $emailConstraint->message = $translator->trans('mautic.core.email.required',
495
            [],
496
            'validators'
497
        );
498
499
        $errors = $this->validator->validate(
500
            $data['email'],
501
            $emailConstraint
502
        );
503
504
        if (0 !== count($errors)) {
505
            foreach ($errors as $error) {
506
                $messages[] = $error->getMessage();
507
            }
508
509
            return $messages;
510
        }
511
512
        $encoder = $this->encoder->getEncoder($user);
513
514
        $user->setFirstName($data['firstname']);
515
        $user->setLastName($data['lastname']);
516
        $user->setUsername($data['username']);
517
        $user->setEmail($data['email']);
518
        $user->setPassword($encoder->encodePassword($data['password'], $user->getSalt()));
519
520
        $adminRole = null;
521
        try {
522
            $adminRole = $entityManager->getReference('MauticUserBundle:Role', 1);
523
        } catch (\Exception $exception) {
524
            $messages['error'] = $translator->trans(
525
                'mautic.installer.error.getting.role',
526
                ['%exception%' => $exception->getMessage()],
527
                'flashes'
528
            );
529
        }
530
531
        if (!empty($adminRole)) {
532
            $user->setRole($adminRole);
533
534
            try {
535
                $entityManager->persist($user);
536
                $entityManager->flush();
537
                $messages = true;
538
            } catch (\Exception $exception) {
539
                $messages['error'] = $translator->trans(
540
                    'mautic.installer.error.creating.user',
541
                    ['%exception%' => $exception->getMessage()],
542
                    'flashes'
543
                );
544
            }
545
        }
546
547
        return $messages;
548
    }
549
550
    /**
551
     * Setup the email configuration.
552
     *
553
     * @param StepInterface|null $step
554
     * @param array              data
555
     *
556
     * @return array|bool
557
     */
558
    public function setupEmailStep($step, $data)
559
    {
560
        $translator = $this->translator;
561
562
        $required = [
563
            'mailer_from_name',
564
            'mailer_from_email',
565
        ];
566
567
        $messages = [];
568
        foreach ($required as $r) {
569
            if (!isset($data[$r]) || empty($data[$r])) {
570
                $messages[$r] = $translator->trans(
571
                    'mautic.core.value.required',
572
                    [],
573
                    'validators'
574
                );
575
            }
576
        }
577
578
        if (!empty($messages)) {
579
            return $messages;
580
        }
581
582
        $emailConstraint          = new Assert\Email();
583
        $emailConstraint->message = $translator->trans('mautic.core.email.required',
584
            [],
585
            'validators'
586
        );
587
588
        $errors = $this->validator->validate(
589
            $data['mailer_from_email'],
590
            $emailConstraint
591
        );
592
593
        if (0 !== count($errors)) {
594
            foreach ($errors as $error) {
595
                $messages[] = $error->getMessage();
596
            }
597
        } else {
598
            $messages = $this->saveConfiguration($data, $step, true);
599
        }
600
601
        return $messages;
602
    }
603
604
    /**
605
     * Create the final configuration.
606
     *
607
     * @param string $siteUrl
608
     *
609
     * @return array|bool
610
     */
611
    public function createFinalConfigStep($siteUrl)
612
    {
613
        // Merge final things into the config, wipe the container, and we're done!
614
        $finalConfigVars = [
615
            'secret_key' => EncryptionHelper::generateKey(),
616
            'site_url'   => $siteUrl,
617
        ];
618
619
        return $this->saveConfiguration($finalConfigVars, null, true);
620
    }
621
622
    /**
623
     * Final migration step for install.
624
     *
625
     * @return bool
626
     *
627
     * @throws \Exception
628
     */
629
    public function finalMigrationStep()
630
    {
631
        // Add database migrations up to this point since this is a fresh install (must be done at this point
632
        // after the cache has been rebuilt
633
        $input  = new ArgvInput(['console', 'doctrine:migrations:version', '--add', '--all', '--no-interaction']);
634
        $output = new BufferedOutput();
635
636
        $application = new Application($this->kernel);
637
        $application->setAutoExit(false);
638
        $application->run($input, $output);
639
640
        return true;
641
    }
642
}
643