step_active()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 3
nc 2
nop 1
dl 0
loc 5
rs 10
c 0
b 0
f 0
1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
use Chamilo\CoreBundle\DataFixtures\LanguageFixtures;
6
use Chamilo\CoreBundle\Entity\AccessUrl;
7
use Chamilo\CoreBundle\Entity\User;
8
use Chamilo\CoreBundle\Entity\UserAuthSource;
9
use Chamilo\CoreBundle\Framework\Container;
10
use Chamilo\CoreBundle\Repository\GroupRepository;
11
use Chamilo\CoreBundle\Repository\Node\AccessUrlRepository;
12
use Chamilo\CoreBundle\Tool\ToolChain;
13
use Doctrine\DBAL\Connection;
14
use Doctrine\Migrations\Configuration\Connection\ExistingConnection;
15
use Doctrine\Migrations\Configuration\Migration\PhpFile;
16
use Doctrine\Migrations\DependencyFactory;
17
use Doctrine\Migrations\Query\Query;
18
use Doctrine\ORM\EntityManager;
19
use Symfony\Component\Console\Output\NullOutput;
20
use Symfony\Component\DependencyInjection\Container as SymfonyContainer;
21
use Symfony\Component\Dotenv\Dotenv;
22
use Symfony\Component\Console\Input\ArrayInput;
23
use Symfony\Component\Console\Output\BufferedOutput;
24
use Symfony\Bundle\FrameworkBundle\Console\Application;
25
26
/*
27
 * Chamilo LMS
28
 * This file contains functions used by the install and upgrade scripts.
29
 *
30
 * Ideas for future additions:
31
 * - a function get_old_version_settings to retrieve the config file settings
32
 *   of older versions before upgrading.
33
 */
34
define('SYSTEM_CONFIG_FILENAME', 'configuration.dist.php');
35
36
/**
37
 * This function detects whether the system has been already installed.
38
 * It should be used for prevention from second running the installation
39
 * script and as a result - destroying a production system.
40
 *
41
 * @return bool The detected result;
42
 *
43
 * @author Ivan Tcholakov, 2010;
44
 */
45
function isAlreadyInstalledSystem()
46
{
47
    global $new_version, $_configuration;
48
49
    if (empty($new_version)) {
50
        return true; // Must be initialized.
51
    }
52
53
    $current_config_file = api_get_path(CONFIGURATION_PATH).'configuration.php';
54
    if (!file_exists($current_config_file)) {
55
        return false; // Configuration file does not exist, install the system.
56
    }
57
    require $current_config_file;
58
59
    $current_version = null;
60
    if (isset($_configuration['system_version'])) {
61
        $current_version = trim($_configuration['system_version']);
62
    }
63
64
    // If the current version is old, upgrading is assumed, the installer goes ahead.
65
    return empty($current_version) ? false : version_compare($current_version, $new_version, '>=');
66
}
67
68
/**
69
 * This function checks if a php extension exists or not and returns an HTML status string.
70
 *
71
 * @param string $extensionName Name of the PHP extension to be checked
72
 * @param string $returnSuccess Text to show when extension is available (defaults to 'Yes')
73
 * @param string $returnFailure Text to show when extension is available (defaults to 'No')
74
 * @param bool   $optional      Whether this extension is optional (then show unavailable text in orange rather than
75
 *                              red)
76
 * @param string $enabledTerm   If this string is not null, then use to check if the corresponding parameter is = 1.
77
 *                              If not, mention it's present but not enabled. For example, for opcache, this should be
78
 *                              'opcache.enable'
79
 *
80
 * @return array
81
 *
82
 * @author  Christophe Gesch??
83
 * @author  Patrick Cool <[email protected]>, Ghent University
84
 * @author  Yannick Warnier <[email protected]>
85
 */
86
function checkExtension(
87
    string $extensionName,
88
    string $returnSuccess = 'Yes',
89
    string $returnFailure = 'No',
90
    bool $optional = false,
91
    string $enabledTerm = ''
92
): array {
93
    if (extension_loaded($extensionName)) {
94
        if (!empty($enabledTerm)) {
95
            $isEnabled = ini_get($enabledTerm);
96
            if ('1' == $isEnabled) {
97
                return [
98
                    'severity' => 'success',
99
                    'message' => $returnSuccess,
100
                ];
101
            } else {
102
                if ($optional) {
103
                    return [
104
                        'severity' => 'warning',
105
                        'message' => get_lang('Extension installed but not enabled'),
106
                    ];
107
                }
108
109
                return [
110
                    'severity' => 'danger',
111
                    'message' => get_lang('Extension installed but not enabled'),
112
                ];
113
            }
114
        }
115
116
        return [
117
            'severity' => 'success',
118
            'message' => $returnSuccess,
119
        ];
120
    } else {
121
        if ($optional) {
122
            return [
123
                'severity' => 'warning',
124
                'message' => $returnFailure,
125
            ];
126
        }
127
128
        return [
129
            'severity' => 'danger',
130
            'message' => $returnFailure,
131
        ];
132
    }
133
}
134
135
/**
136
 * This function checks whether a php setting matches the recommended value.
137
 *
138
 * @param string $phpSetting       A PHP setting to check
139
 * @param string $recommendedValue A recommended value to show on screen
140
 *
141
 * @author Patrick Cool <[email protected]>, Ghent University
142
 */
143
function checkPhpSetting(
144
    string $phpSetting,
145
    string $recommendedValue
146
): array {
147
    $currentPhpValue = getPhpSetting($phpSetting);
148
149
    return $currentPhpValue == $recommendedValue
150
        ? ['severity' => 'success', 'value' => $currentPhpValue]
151
        : ['severity' => 'danger', 'value' => $currentPhpValue];
152
}
153
154
/**
155
 * This function return the value of a php.ini setting if not "" or if exists,
156
 * otherwise return false.
157
 *
158
 * @param string $phpSetting The name of a PHP setting
159
 *
160
 * @return mixed The value of the setting, or false if not found
161
 */
162
function checkPhpSettingExists($phpSetting)
163
{
164
    if ('' != ini_get($phpSetting)) {
165
        return ini_get($phpSetting);
166
    }
167
168
    return false;
169
}
170
171
/**
172
 * Returns a textual value ('ON' or 'OFF') based on a requester 2-state ini- configuration setting.
173
 *
174
 * @param string $val a php ini value
175
 *
176
 * @return bool ON or OFF
177
 *
178
 * @author Joomla <http://www.joomla.org>
179
 */
180
function getPhpSetting($val)
181
{
182
    $value = ini_get($val);
183
    switch ($val) {
184
        case 'display_errors':
185
            global $originalDisplayErrors;
186
            $value = $originalDisplayErrors;
187
            break;
188
    }
189
190
    return '1' == $value ? 'ON' : 'OFF';
191
}
192
193
/**
194
 * This function returns a string "true" or "false" according to the passed parameter.
195
 *
196
 * @param int $var The variable to present as text
197
 *
198
 * @return string the string "true" or "false"
199
 *
200
 * @author Christophe Gesch??
201
 */
202
function trueFalse($var)
203
{
204
    return $var ? 'true' : 'false';
205
}
206
207
/**
208
 * This function checks if the given folder is writable.
209
 *
210
 * @param string $folder     Full path to a folder
211
 * @param bool   $suggestion Whether to show a suggestion or not
212
 *
213
 * @return string
214
 */
215
function check_writable($folder, $suggestion = false)
216
{
217
    if (is_writable($folder)) {
218
        return Display::label(get_lang('Writable'), 'success');
219
    } else {
220
        return Display::label(get_lang('Not writable'), 'important');
221
    }
222
}
223
224
/**
225
 * This function checks if the given folder is readable.
226
 *
227
 * @param string $folder     Full path to a folder
228
 * @param bool   $suggestion Whether to show a suggestion or not
229
 *
230
 * @return string
231
 */
232
function checkReadable($folder, $suggestion = false)
233
{
234
    if (is_readable($folder)) {
235
        return Display::label(get_lang('Readable'), 'success');
236
    } else {
237
        if ($suggestion) {
238
            return Display::label(get_lang('Not readable'), 'info');
239
        } else {
240
            return Display::label(get_lang('Not readable'), 'important');
241
        }
242
    }
243
}
244
245
/**
246
 * We assume this function is called from install scripts that reside inside the install folder.
247
 */
248
function set_file_folder_permissions()
249
{
250
    @chmod('.', 0755); //set permissions on install dir
251
    @chmod('..', 0755); //set permissions on parent dir of install dir
252
}
253
254
/**
255
 * This function returns the value of a parameter from the configuration file.
256
 *
257
 * WARNING - this function relies heavily on global variables $updateFromConfigFile
258
 * and $configFile, and also changes these globals. This can be rewritten.
259
 *
260
 * @param string $param      the parameter of which the value is returned
261
 * @param string $updatePath If we want to give the path rather than take it from POST
262
 *
263
 * @return string the value of the parameter
264
 *
265
 * @author Olivier Brouckaert
266
 * @author Reworked by Ivan Tcholakov, 2010
267
 */
268
function get_config_param($param, $updatePath = '')
269
{
270
    global $updateFromConfigFile;
271
    if (empty($updatePath) && !empty($_POST['updatePath'])) {
272
        $updatePath = $_POST['updatePath'];
273
    }
274
275
    if (empty($updatePath)) {
276
        $updatePath = api_get_path(SYMFONY_SYS_PATH);
277
    }
278
    $updatePath = api_add_trailing_slash(str_replace('\\', '/', realpath($updatePath)));
279
280
    if (empty($updateFromConfigFile)) {
281
        // If update from previous install was requested,
282
        if (file_exists($updatePath.'app/config/configuration.php')) {
283
            $updateFromConfigFile = 'app/config/configuration.php';
284
        } else {
285
            // Give up recovering.
286
            return null;
287
        }
288
    }
289
290
    if (file_exists($updatePath.$updateFromConfigFile) &&
291
        !is_dir($updatePath.$updateFromConfigFile)
292
    ) {
293
        require $updatePath.$updateFromConfigFile;
294
295
        if (isset($_configuration) && array_key_exists($param, $_configuration)) {
296
            return $_configuration[$param];
297
        }
298
299
        return null;
300
    }
301
302
    error_log('Config array could not be found in get_config_param()', 0);
303
304
    return null;
305
}
306
307
/**
308
 * Gets a configuration parameter from the database. Returns returns null on failure.
309
 *
310
 * @param string $param Name of param we want
311
 *
312
 * @return mixed The parameter value or null if not found
313
 */
314
function get_config_param_from_db($param = '')
315
{
316
    $param = Database::escape_string($param);
317
318
    $schemaManager = Database::getConnection()->createSchemaManager();
319
320
    if ($schemaManager->tablesExist('settings_current')) {
321
        $query = "SELECT * FROM settings_current WHERE variable = '$param'";
322
    } elseif ($schemaManager->tablesExist('settings')) {
323
        $query = "SELECT * FROM settings WHERE variable = '$param'";
324
    } else {
325
        return null;
326
    }
327
328
    if (false !== ($res = Database::query($query))) {
329
        if (Database::num_rows($res) > 0) {
330
            $row = Database::fetch_array($res);
331
            return $row['selected_value'];
332
        }
333
    }
334
335
    return null;
336
}
337
338
/**
339
 * Connect to the database and returns the entity manager.
340
 *
341
 * @param string $host
342
 * @param string $username
343
 * @param string $password
344
 * @param string $databaseName
345
 * @param int $port
346
 *
347
 * @throws \Doctrine\DBAL\Exception
348
 * @throws \Doctrine\ORM\ORMException
349
 *
350
 * @return void
351
 */
352
function connectToDatabase(
353
    $host,
354
    $username,
355
    $password,
356
    $databaseName,
357
    $port = 3306
358
): void
359
{
360
    Database::connect(
361
        [
362
            'driver' => 'pdo_mysql',
363
            'host' => $host,
364
            'port' => $port,
365
            'user' => $username,
366
            'password' => $password,
367
            'dbname' => $databaseName,
368
        ]
369
    );
370
}
371
372
/**
373
 * This function prints class=active_step $current_step=$param.
374
 *
375
 * @param int $param A step in the installer process
376
 *
377
 * @author Patrick Cool <[email protected]>, Ghent University
378
 */
379
function step_active($param)
380
{
381
    global $current_step;
382
    if ($param == $current_step) {
383
        echo 'install-steps__step--active';
384
    }
385
}
386
387
/**
388
 * This function displays the Step X of Y -.
389
 *
390
 * @return string String that says 'Step X of Y' with the right values
391
 */
392
function display_step_sequence()
393
{
394
    global $current_step;
395
396
    return get_lang('Step'.$current_step).' &ndash; ';
397
}
398
399
/**
400
 * Displays a drop down box for selection the preferred language.
401
 */
402
function display_language_selection_box($name = 'language_list', $default_language = 'en_US')
403
{
404
    // Displaying the box.
405
    return Display::select(
406
        'language_list',
407
        array_column(LanguageFixtures::getLanguages(), 'english_name', 'isocode'),
408
        $default_language,
409
        [],
410
        false
411
    );
412
}
413
414
/**
415
 * This function displays the requirements for installing Chamilo.
416
 *
417
 * @param string $updatePath         The updatePath given (if given)
418
 * @param array  $upgradeFromVersion The different subversions from version 1.9
419
 *
420
 * @author unknow
421
 * @author Patrick Cool <[email protected]>, Ghent University
422
 */
423
function display_requirements(
424
    string $installType,
425
    bool $badUpdatePath,
426
    string $updatePath = '',
427
    array $upgradeFromVersion = []
428
): array {
429
    global $_setting, $originalMemoryLimit;
430
431
    $dir = api_get_path(SYS_ARCHIVE_PATH).'temp/';
432
    $fileToCreate = 'test';
433
434
    $perms_dir = [0777, 0755, 0775, 0770, 0750, 0700];
435
    $perms_fil = [0666, 0644, 0664, 0660, 0640, 0600];
436
    $course_test_was_created = false;
437
    $dir_perm_verified = 0777;
438
439
    foreach ($perms_dir as $perm) {
440
        $r = @mkdir($dir, $perm);
441
        if (true === $r) {
442
            $dir_perm_verified = $perm;
443
            $course_test_was_created = true;
444
            break;
445
        }
446
    }
447
448
    $fil_perm_verified = 0666;
449
    $file_course_test_was_created = false;
450
    if (is_dir($dir)) {
451
        foreach ($perms_fil as $perm) {
452
            if ($file_course_test_was_created) {
453
                break;
454
            }
455
            $r = @touch($dir.'/'.$fileToCreate, $perm);
456
            if (true === $r) {
457
                $fil_perm_verified = $perm;
458
                $file_course_test_was_created = true;
459
            }
460
        }
461
    }
462
463
    @unlink($dir.'/'.$fileToCreate);
464
    @rmdir($dir);
465
466
    //  SERVER REQUIREMENTS
467
    $timezone = checkPhpSettingExists('date.timezone');
468
469
    $phpVersion = phpversion();
470
    $isVersionPassed = version_compare($phpVersion, REQUIRED_PHP_VERSION, '<=') <= 1;
471
472
    $extensions = [];
473
    $extensions[] = [
474
        'title' => sprintf(get_lang('%s support'), 'Session'),
475
        'url' => 'https://php.net/manual/en/book.session.php',
476
        'status' => checkExtension(
477
            'session',
478
            get_lang('Yes'),
479
            sprintf(get_lang('%s extension not available'), 'Sessions')
480
        ),
481
    ];
482
    $extensions[] = [
483
        'title' => sprintf(get_lang('%s support'), 'MySQL'),
484
        'url' => 'https://php.net/manual/en/book.mysql.php',
485
        'status' => checkExtension(
486
            'pdo_mysql',
487
            get_lang('Yes'),
488
            sprintf(get_lang('%s extension not available'), 'MySQL')
489
        ),
490
    ];
491
    $extensions[] = [
492
        'title' => sprintf(get_lang('%s support'), 'Zip'),
493
        'url' => 'https://php.net/manual/en/book.zip.php',
494
        'status' => checkExtension(
495
            'zip',
496
            get_lang('Yes'),
497
            sprintf(get_lang('%s extension not available'), 'Zip')
498
        ),
499
    ];
500
    $extensions[] = [
501
        'title' => sprintf(get_lang('%s support'), 'Zlib'),
502
        'url' => 'https://php.net/manual/en/book.zlib.php',
503
        'status' => checkExtension(
504
            'zlib',
505
            get_lang('Yes'),
506
            sprintf(get_lang('%s extension not available'), 'Zlib')
507
        ),
508
    ];
509
    $extensions[] = [
510
        'title' => sprintf(get_lang('%s support'), 'Perl-compatible regular expressions'),
511
        'url' => 'https://php.net/manual/en/book.pcre.php',
512
        'status' => checkExtension(
513
            'pcre',
514
            get_lang('Yes'),
515
            sprintf(get_lang('%s extension not available'), 'PCRE')
516
        ),
517
    ];
518
    $extensions[] = [
519
        'title' => sprintf(get_lang('%s support'), 'XML'),
520
        'url' => 'https://php.net/manual/en/book.xml.php',
521
        'status' => checkExtension(
522
            'xml',
523
            get_lang('Yes'),
524
            get_lang('No')
525
        ),
526
    ];
527
    $extensions[] = [
528
        'title' => sprintf(get_lang('%s support'), 'Internationalisation'),
529
        'url' => 'https://php.net/manual/en/book.intl.php',
530
        'status' => checkExtension(
531
            'intl',
532
            get_lang('Yes'),
533
            get_lang('No')
534
        ),
535
    ];
536
    $extensions[] = [
537
        'title' => sprintf(get_lang('%s support'), 'JSON'),
538
        'url' => 'https://php.net/manual/en/book.json.php',
539
        'status' => checkExtension(
540
            'json',
541
            get_lang('Yes'),
542
            get_lang('No')
543
        ),
544
    ];
545
    $extensions[] = [
546
        'title' => sprintf(get_lang('%s support'), 'GD'),
547
        'url' => 'https://php.net/manual/en/book.image.php',
548
        'status' => checkExtension(
549
            'gd',
550
            get_lang('Yes'),
551
            sprintf(get_lang('%s extension not available'), 'GD')
552
        ),
553
    ];
554
    $extensions[] = [
555
        'title' => sprintf(get_lang('%s support'), 'cURL'),
556
        'url' => 'https://php.net/manual/en/book.curl.php',
557
        'status' => checkExtension(
558
            'curl',
559
            get_lang('Yes'),
560
            get_lang('No')
561
        ),
562
    ];
563
    $extensions[] = [
564
        'title' => sprintf(get_lang('%s support'), 'Multibyte string'),
565
        'url' => 'https://php.net/manual/en/book.mbstring.php',
566
        'status' => checkExtension(
567
            'mbstring',
568
            get_lang('Yes'),
569
            sprintf(get_lang('%s extension not available'), 'MBString'),
570
            true
571
        ),
572
    ];
573
    $extensions[] = [
574
        'title' => sprintf(get_lang('%s support'), 'Exif'),
575
        'url' => 'https://php.net/manual/en/book.exif.php',
576
        'status' => checkExtension(
577
            'exif',
578
            get_lang('Yes'),
579
            sprintf(get_lang('%s extension not available'), 'Exif'),
580
            true
581
        ),
582
    ];
583
    $extensions[] = [
584
        'title' => sprintf(get_lang('%s support'), 'Zend OpCache'),
585
        'url' => 'https://php.net/opcache',
586
        'status' => checkExtension(
587
            'Zend OPcache',
588
            get_lang('Yes'),
589
            get_lang('No'),
590
            true,
591
            'opcache.enable'
592
        ),
593
    ];
594
    $extensions[] = [
595
        'title' => sprintf(get_lang('%s support'), 'APCu'),
596
        'url' => 'https://php.net/apcu',
597
        'status' => checkExtension(
598
            'apcu',
599
            get_lang('Yes'),
600
            get_lang('No'),
601
            true,
602
            'apc.enabled'
603
        ),
604
    ];
605
    $extensions[] = [
606
        'title' => sprintf(get_lang('%s support'), 'Iconv'),
607
        'url' => 'https://php.net/manual/en/book.iconv.php',
608
        'status' => checkExtension(
609
            'iconv',
610
            get_lang('Yes'),
611
            get_lang('No'),
612
            true
613
        ),
614
    ];
615
    $extensions[] = [
616
        'title' => sprintf(get_lang('%s support'), 'LDAP'),
617
        'url' => 'https://php.net/manual/en/book.ldap.php',
618
        'status' => checkExtension(
619
            'ldap',
620
            get_lang('Yes'),
621
            sprintf(get_lang('%s extension not available'), 'LDAP'),
622
            true
623
        ),
624
    ];
625
626
    $extensions[] = [
627
        'title' => sprintf(get_lang('%s support'), 'Xapian'),
628
        'url' => 'https://xapian.org/',
629
        'status' => checkExtension(
630
            'xapian',
631
            get_lang('Yes'),
632
            get_lang('No'),
633
            true
634
        ),
635
    ];
636
637
    // RECOMMENDED SETTINGS
638
    // Note: these are the settings for Joomla, does this also apply for Chamilo?
639
    // Note: also add upload_max_filesize here so that large uploads are possible
640
    $phpIni = [];
641
    $phpIni[] = [
642
        'title' => 'Display Errors',
643
        'url' => 'https://php.net/manual/ref.errorfunc.php#ini.display-errors',
644
        'recommended' => 'OFF',
645
        'current' => checkPhpSetting('display_errors', 'OFF'),
646
    ];
647
    $phpIni[] = [
648
        'title' => 'File Uploads',
649
        'url' => 'https://php.net/manual/ini.core.php#ini.file-uploads',
650
        'recommended' => 'ON',
651
        'current' => checkPhpSetting('file_uploads', 'ON'),
652
    ];
653
    $phpIni[] = [
654
        'title' => 'Session auto start',
655
        'url' => 'https://php.net/manual/ref.session.php#ini.session.auto-start',
656
        'recommended' => 'OFF',
657
        'current' => checkPhpSetting('session.auto_start', 'OFF'),
658
    ];
659
    $phpIni[] = [
660
        'title' => 'Short Open Tag',
661
        'url' => 'https://php.net/manual/ini.core.php#ini.short-open-tag',
662
        'recommended' => 'OFF',
663
        'current' => checkPhpSetting('short_open_tag', 'OFF'),
664
    ];
665
    $phpIni[] = [
666
        'title' => 'Cookie HTTP Only',
667
        'url' => 'https://www.php.net/manual/en/session.configuration.php#ini.session.cookie-httponly',
668
        'recommended' => 'ON',
669
        'current' => checkPhpSetting('session.cookie_httponly', 'ON'),
670
    ];
671
    $phpIni[] = [
672
        'title' => 'Maximum upload file size',
673
        'url' => 'https://php.net/manual/ini.core.php#ini.upload-max-filesize',
674
        'recommended' => '>= '.REQUIRED_MIN_UPLOAD_MAX_FILESIZE.'M',
675
        'current' => compare_setting_values(ini_get('upload_max_filesize'), REQUIRED_MIN_UPLOAD_MAX_FILESIZE),
676
    ];
677
    $phpIni[] = [
678
        'title' => 'Maximum post size',
679
        'url' => 'https://php.net/manual/ini.core.php#ini.post-max-size',
680
        'recommended' => '>= '.REQUIRED_MIN_POST_MAX_SIZE.'M',
681
        'current' => compare_setting_values(ini_get('post_max_size'), REQUIRED_MIN_POST_MAX_SIZE),
682
    ];
683
    $phpIni[] = [
684
        'title' => 'Memory Limit',
685
        'url' => 'https://www.php.net/manual/en/ini.core.php#ini.memory-limit',
686
        'recommended' => '>= '.REQUIRED_MIN_MEMORY_LIMIT.'M',
687
        'current' => compare_setting_values($originalMemoryLimit, REQUIRED_MIN_MEMORY_LIMIT),
688
    ];
689
690
    // DIRECTORY AND FILE PERMISSIONS
691
    $_SESSION['permissions_for_new_directories'] = $_setting['permissions_for_new_directories'] = $dir_perm_verified;
692
    $_SESSION['permissions_for_new_files'] = $_setting['permissions_for_new_files'] = $fil_perm_verified;
693
694
    $dirPerm = '0'.decoct($dir_perm_verified);
695
    $filePerm = '0'.decoct($fil_perm_verified);
696
697
    $pathPermissions = [];
698
699
    if (file_exists(api_get_path(SYS_CODE_PATH).'inc/conf/configuration.php')) {
700
        $pathPermissions[] = [
701
            'requirement' => api_get_path(SYS_CODE_PATH).'inc/conf',
702
            'status' => is_writable(api_get_path(SYS_CODE_PATH).'inc/conf'),
703
        ];
704
    }
705
    $basePath = api_get_path(SYMFONY_SYS_PATH);
706
707
    $pathPermissions[] = [
708
        'item' => $basePath.'var/',
709
        'status' => is_writable($basePath.'var'),
710
    ];
711
    $pathPermissions[] = [
712
        'item' => $basePath.'config/',
713
        'status' => is_writable($basePath.'config'),
714
    ];
715
    $pathPermissions[] = [
716
        'item' => $basePath.'.env',
717
        'status' => checkCanCreateFile($basePath.'.env'),
718
    ];
719
    $pathPermissions[] = [
720
        'item' => get_lang('Permissions for new directories'),
721
        'status' => $dirPerm,
722
    ];
723
    $pathPermissions[] = [
724
        'item' => get_lang('Permissions for new files'),
725
        'status' => $filePerm,
726
    ];
727
728
    $notWritable = [];
729
    $deprecatedToRemove = [];
730
731
    $error = false;
732
733
    if ('update' !== $installType || !empty($updatePath) && !$badUpdatePath) {
734
        // First, attempt to set writing permissions if we don't have them yet
735
        //$perm = api_get_permissions_for_new_directories();
736
        $perm = octdec('0777');
737
        //$perm_file = api_get_permissions_for_new_files();
738
        $perm_file = octdec('0666');
739
740
        if (!$course_test_was_created) {
741
            error_log('Installer: Could not create test course - Make sure permissions are fine.');
742
            $error = true;
743
        }
744
745
        $checked_writable = api_get_path(CONFIGURATION_PATH).'configuration.php';
746
        if (file_exists($checked_writable) && !is_writable($checked_writable)) {
747
            $notWritable[] = $checked_writable;
748
            @chmod($checked_writable, $perm_file);
749
        }
750
751
        // Second, if this fails, report an error
752
        //--> The user would have to adjust the permissions manually
753
        if (count($notWritable) > 0) {
754
            error_log('Installer: At least one needed directory or file is not writeable');
755
            $error = true;
756
        }
757
758
        $deprecated = [
759
            api_get_path(SYS_CODE_PATH).'exercice/',
760
            api_get_path(SYS_CODE_PATH).'newscorm/',
761
            api_get_path(SYS_PLUGIN_PATH).'ticket/',
762
            api_get_path(SYS_PLUGIN_PATH).'skype/',
763
        ];
764
765
        foreach ($deprecated as $deprecatedDirectory) {
766
            if (!is_dir($deprecatedDirectory)) {
767
                continue;
768
            }
769
            $deprecatedToRemove[] = $deprecatedDirectory;
770
        }
771
    }
772
773
    return [
774
        'timezone' => $timezone,
775
        'isVersionPassed' => $isVersionPassed,
776
        'phpVersion' => $phpVersion,
777
        'extensions' => $extensions,
778
        'phpIni' => $phpIni,
779
        'pathPermissions' => $pathPermissions,
780
        'step2_update_6' => isset($_POST['step2_update_6']),
781
        'notWritable' => $notWritable,
782
        'existsConfigurationFile' => false,
783
        'deprecatedToRemove' => $deprecatedToRemove,
784
        'installError' => $error,
785
    ];
786
}
787
788
/**
789
 * Displays the license (GNU GPL) as step 2, with
790
 * - an "I accept" button named step3 to proceed to step 3;
791
 * - a "Back" button named step1 to go back to the first step.
792
 */
793
function display_license_agreement(): array
794
{
795
    $license = api_htmlentities(@file_get_contents(api_get_path(SYMFONY_SYS_PATH).'public/documentation/license.txt'));
796
797
    $activtiesList = [
798
        ['Advertising/Marketing/PR'],
799
        ['Agriculture/Forestry'],
800
        ['Architecture'],
801
        ['Banking/Finance'],
802
        ['Biotech/Pharmaceuticals'],
803
        ['Business Equipment'],
804
        ['Business Services'],
805
        ['Construction'],
806
        ['Consulting/Research'],
807
        ['Education'],
808
        ['Engineering'],
809
        ['Environmental'],
810
        ['Government'],
811
        ['Health Care'],
812
        ['Hospitality/Lodging/Travel'],
813
        ['Insurance'],
814
        ['Legal'],
815
        ['Manufacturing'],
816
        ['Media/Entertainment'],
817
        ['Mortgage'],
818
        ['Non-Profit'],
819
        ['Real Estate'],
820
        ['Restaurant'],
821
        ['Retail'],
822
        ['Shipping/Transportation'],
823
        ['Technology'],
824
        ['Telecommunications'],
825
        ['Other'],
826
    ];
827
828
    $rolesList = [
829
        ['Administration'],
830
        ['CEO/President/ Owner'],
831
        ['CFO'],
832
        ['CIO/CTO'],
833
        ['Consultant'],
834
        ['Customer Service'],
835
        ['Engineer/Programmer'],
836
        ['Facilities/Operations'],
837
        ['Finance/ Accounting Manager'],
838
        ['Finance/ Accounting Staff'],
839
        ['General Manager'],
840
        ['Human Resources'],
841
        ['IS/IT Management'],
842
        ['IS/ IT Staff'],
843
        ['Marketing Manager'],
844
        ['Marketing Staff'],
845
        ['Partner/Principal'],
846
        ['Purchasing Manager'],
847
        ['Sales/ Business Dev. Manager'],
848
        ['Sales/ Business Dev.'],
849
        ['Vice President/Senior Manager'],
850
        ['Other'],
851
    ];
852
853
    $countriesList = array_map(
854
        fn ($country) => [$country],
855
        get_countries_list_from_array()
856
    );
857
858
    $languagesList = [
859
        ['bulgarian', 'Bulgarian'],
860
        ['indonesian', 'Bahasa Indonesia'],
861
        ['bosnian', 'Bosanski'],
862
        ['german', 'Deutsch'],
863
        ['english', 'English'],
864
        ['spanish', 'Spanish'],
865
        ['french', 'Français'],
866
        ['italian', 'Italian'],
867
        ['hungarian', 'Magyar'],
868
        ['dutch', 'Nederlands'],
869
        ['brazilian', 'Português do Brasil'],
870
        ['portuguese', 'Português europeu'],
871
        ['slovenian', 'Slovenčina'],
872
    ];
873
874
    return [
875
        'license' => $license,
876
        'activitiesList' => $activtiesList,
877
        'rolesList' => $rolesList,
878
        'countriesList' => $countriesList,
879
        'languagesList' => $languagesList,
880
    ];
881
}
882
883
/**
884
 * Displays a parameter in a table row.
885
 * Used by the display_database_settings_form function.
886
 *
887
 * @param   string  Type of install
888
 * @param   string  Name of parameter
889
 * @param   string  Field name (in the HTML form)
890
 * @param   string  Field value
891
 * @param   string  Extra notice (to show on the right side)
892
 * @param   bool Whether to display in update mode
893
 * @param   string  Additional attribute for the <tr> element
894
 */
895
function displayDatabaseParameter(
896
    $installType,
897
    $parameterName,
898
    $formFieldName,
899
    $parameterValue,
900
    $extra_notice,
901
    $displayWhenUpdate = true
902
) {
903
    echo "<dt class='col-sm-4'>$parameterName</dt>";
904
    echo '<dd class="col-sm-8">';
905
    if (INSTALL_TYPE_UPDATE == $installType && $displayWhenUpdate) {
906
        echo '<input
907
                type="hidden"
908
                name="'.$formFieldName.'"
909
                id="'.$formFieldName.'"
910
                value="'.api_htmlentities($parameterValue).'" />'.$parameterValue;
911
    } else {
912
        $inputType = 'dbPassForm' === $formFieldName ? 'password' : 'text';
913
        //Slightly limit the length of the database prefix to avoid having to cut down the databases names later on
914
        $maxLength = 'dbPrefixForm' === $formFieldName ? '15' : MAX_FORM_FIELD_LENGTH;
915
        if (INSTALL_TYPE_UPDATE == $installType) {
916
            echo '<input
917
                type="hidden" name="'.$formFieldName.'" id="'.$formFieldName.'"
918
                value="'.api_htmlentities($parameterValue).'" />';
919
            echo api_htmlentities($parameterValue);
920
        } else {
921
            echo '<input
922
                        type="'.$inputType.'"
923
                        class="form-control"
924
                        size="'.DATABASE_FORM_FIELD_DISPLAY_LENGTH.'"
925
                        maxlength="'.$maxLength.'"
926
                        name="'.$formFieldName.'"
927
                        id="'.$formFieldName.'"
928
                        value="'.api_htmlentities($parameterValue).'" />
929
                    '.$extra_notice.'
930
                  ';
931
        }
932
    }
933
    echo '</dd>';
934
}
935
936
/**
937
 * Displays step 3 - a form where the user can enter the installation settings
938
 * regarding the databases - login and password, names, prefixes, single
939
 * or multiple databases, tracking or not...
940
 */
941
function display_database_settings_form(
942
    string $installType,
943
    string $dbHostForm,
944
    string $dbUsernameForm,
945
    string $dbPassForm,
946
    string $dbNameForm,
947
    int $dbPortForm = 3306
948
): array {
949
    if ('update' === $installType) {
950
        $dbHostForm = get_config_param('db_host');
951
        $dbUsernameForm = get_config_param('db_user');
952
        $dbPassForm = get_config_param('db_password');
953
        $dbNameForm = get_config_param('main_database');
954
        $dbPortForm = get_config_param('db_port');
955
    }
956
957
    $databaseExists = false;
958
    $databaseConnectionError = '';
959
    $connectionParams = null;
960
961
    try {
962
        if ('update' === $installType) {
963
            connectToDatabase(
964
                $dbHostForm,
965
                $dbUsernameForm,
966
                $dbPassForm,
967
                $dbNameForm,
968
                $dbPortForm
969
            );
970
971
            $manager = Database::getManager();
972
            $connection = $manager->getConnection();
973
            $connection->connect();
974
            $schemaManager = $connection->getSchemaManager();
975
976
            // Test create/alter/drop table
977
            $table = 'zXxTESTxX_'.mt_rand(0, 1000);
978
            $sql = "CREATE TABLE $table (id INT AUTO_INCREMENT NOT NULL, name varchar(255), PRIMARY KEY(id))";
979
            $connection->executeQuery($sql);
980
            $tableCreationWorks = false;
981
            $tableDropWorks = false;
982
            if ($schemaManager->tablesExist($table)) {
983
                $sql = "ALTER TABLE $table ADD COLUMN name2 varchar(140) ";
984
                $connection->executeQuery($sql);
985
                $schemaManager->dropTable($table);
986
                $tableDropWorks = false === $schemaManager->tablesExist($table);
987
            }
988
        } else {
989
            connectToDatabase(
990
                $dbHostForm,
991
                $dbUsernameForm,
992
                $dbPassForm,
993
                null,
994
                $dbPortForm
995
            );
996
997
            $manager = Database::getManager();
998
            $schemaManager = $manager->getConnection()->createSchemaManager();
999
            $databases = $schemaManager->listDatabases();
1000
            $databaseExists = in_array($dbNameForm, $databases);
1001
        }
1002
    } catch (Exception $e) {
1003
        $databaseConnectionError = $e->getMessage();
1004
        $manager = null;
1005
    }
1006
1007
    if ($manager && $manager->getConnection()->isConnected()) {
1008
        $connectionParams = $manager->getConnection()->getParams();
1009
    }
1010
1011
    return [
1012
        'dbHostForm' => $dbHostForm,
1013
        'dbPortForm' => $dbPortForm,
1014
        'dbUsernameForm' => $dbUsernameForm,
1015
        'dbPassForm' => $dbPassForm,
1016
        'dbNameForm' => $dbNameForm,
1017
        'examplePassword' => api_generate_password(8, false),
1018
        'dbExists' => $databaseExists,
1019
        'dbConnError' => $databaseConnectionError,
1020
        'connParams' => $connectionParams,
1021
    ];
1022
}
1023
1024
/**
1025
 * Displays a parameter in a table row.
1026
 * Used by the display_configuration_settings_form function.
1027
 *
1028
 * @param string $installType
1029
 * @param string $parameterName
1030
 * @param string $formFieldName
1031
 * @param string $parameterValue
1032
 * @param string $displayWhenUpdate
1033
 *
1034
 * @return string
1035
 */
1036
function display_configuration_parameter(
1037
    $installType,
1038
    $parameterName,
1039
    $formFieldName,
1040
    $parameterValue,
1041
    $displayWhenUpdate = 'true'
1042
) {
1043
    $html = '<div class="form-group row">';
1044
    $html .= '<label class="col-sm-6 p-2 control-label">'.$parameterName.'</label>';
1045
    if (INSTALL_TYPE_UPDATE == $installType && $displayWhenUpdate) {
1046
        $html .= '<input
1047
            type="hidden"
1048
            name="'.$formFieldName.'"
1049
            value="'.api_htmlentities($parameterValue, ENT_QUOTES).'" />'.$parameterValue;
1050
    } else {
1051
        $html .= '<div class="col-sm-6">
1052
                    <input
1053
                        class="form-control"
1054
                        type="text"
1055
                        size="'.FORM_FIELD_DISPLAY_LENGTH.'"
1056
                        maxlength="'.MAX_FORM_FIELD_LENGTH.'"
1057
                        name="'.$formFieldName.'"
1058
                        value="'.api_htmlentities($parameterValue, ENT_QUOTES).'" />
1059
                    '.'</div>';
1060
    }
1061
    $html .= '</div>';
1062
1063
    return $html;
1064
}
1065
1066
/**
1067
 * Displays step 4 of the installation - configuration settings about Chamilo itself.
1068
 */
1069
function display_configuration_settings_form(
1070
    string $installType,
1071
    string $urlForm,
1072
    string $languageForm,
1073
    string $emailForm,
1074
    string $adminFirstName,
1075
    string $adminLastName,
1076
    string $adminPhoneForm,
1077
    string $campusForm,
1078
    string $institutionForm,
1079
    string $institutionUrlForm,
1080
    string $encryptPassForm,
1081
    string $allowSelfReg,
1082
    string $allowSelfRegProf,
1083
    string $loginForm,
1084
    string $passForm
1085
): array {
1086
    if ('update' !== $installType && empty($languageForm)) {
1087
        $languageForm = $_SESSION['install_language'];
1088
    }
1089
1090
    $stepData = [];
1091
1092
    if ('update' === $installType) {
1093
        $stepData['rootWeb'] = get_config_param('root_web');
1094
        $stepData['rootSys'] = get_config_param('root_sys');
1095
        $stepData['systemVersion'] = get_config_param('system_version');
1096
    }
1097
1098
    $stepData['loginForm'] = $loginForm;
1099
    $stepData['passForm'] = $passForm;
1100
    $stepData['adminFirstName'] = $adminFirstName;
1101
    $stepData['adminLastName'] = $adminLastName;
1102
    $stepData['emailForm'] = $emailForm;
1103
    $stepData['adminPhoneForm'] = $adminPhoneForm;
1104
    $stepData['languageForm'] = $languageForm;
1105
    $stepData['urlForm'] = $urlForm;
1106
    $stepData['campusForm'] = $campusForm;
1107
    $stepData['institutionForm'] = $institutionForm;
1108
    $stepData['institutionUrlForm'] = $institutionUrlForm;
1109
    $stepData['encryptPassForm'] = $encryptPassForm;
1110
    $stepData['allowSelfReg'] = $allowSelfReg;
1111
    $stepData['allowSelfRegProf'] = $allowSelfRegProf;
1112
1113
    return $stepData;
1114
}
1115
1116
/**
1117
 * This function return countries list from array (hardcoded).
1118
 *
1119
 * @param bool $combo (Optional) True for returning countries list with select html
1120
 *
1121
 * @return array|string countries list
1122
 */
1123
function get_countries_list_from_array($combo = false)
1124
{
1125
    $a_countries = [
1126
        'Afghanistan', 'Albania', 'Algeria', 'Andorra', 'Angola', 'Antigua and Barbuda', 'Argentina', 'Armenia', 'Australia', 'Austria', 'Azerbaijan',
1127
        'Bahamas', 'Bahrain', 'Bangladesh', 'Barbados', 'Belarus', 'Belgium', 'Belize', 'Benin', 'Bhutan', 'Bolivia', 'Bosnia and Herzegovina', 'Botswana', 'Brazil', 'Brunei', 'Bulgaria', 'Burkina Faso', 'Burundi',
1128
        'Cambodia', 'Cameroon', 'Canada', 'Cape Verde', 'Central African Republic', 'Chad', 'Chile', 'China', 'Colombi', 'Comoros', 'Congo (Brazzaville)', 'Congo', 'Costa Rica', "Cote d'Ivoire", 'Croatia', 'Cuba', 'Cyprus', 'Czech Republic',
1129
        'Denmark', 'Djibouti', 'Dominica', 'Dominican Republic',
1130
        'East Timor (Timor Timur)', 'Ecuador', 'Egypt', 'El Salvador', 'Equatorial Guinea', 'Eritrea', 'Estonia', 'Ethiopia',
1131
        'Fiji', 'Finland', 'France',
1132
        'Gabon', 'Gambia, The', 'Georgia', 'Germany', 'Ghana', 'Greece', 'Grenada', 'Guatemala', 'Guinea', 'Guinea-Bissau', 'Guyana',
1133
        'Haiti', 'Honduras', 'Hungary',
1134
        'Iceland', 'India', 'Indonesia', 'Iran', 'Iraq', 'Ireland', 'Israel', 'Italy',
1135
        'Jamaica', 'Japan', 'Jordan',
1136
        'Kazakhstan', 'Kenya', 'Kiribati', 'Korea, North', 'Korea, South', 'Kuwait', 'Kyrgyzstan',
1137
        'Laos', 'Latvia', 'Lebanon', 'Lesotho', 'Liberia', 'Libya', 'Liechtenstein', 'Lithuania', 'Luxembourg',
1138
        'Macedonia', 'Madagascar', 'Malawi', 'Malaysia', 'Maldives', 'Mali', 'Malta', 'Marshall Islands', 'Mauritania', 'Mauritius', 'Mexico', 'Micronesia', 'Moldova', 'Monaco', 'Mongolia', 'Morocco', 'Mozambique', 'Myanmar',
1139
        'Namibia', 'Nauru', 'Nepa', 'Netherlands', 'New Zealand', 'Nicaragua', 'Niger', 'Nigeria', 'Norway',
1140
        'Oman',
1141
        'Pakistan', 'Palau', 'Panama', 'Papua New Guinea', 'Paraguay', 'Peru', 'Philippines', 'Poland', 'Portugal',
1142
        'Qatar',
1143
        'Romania', 'Russia', 'Rwanda',
1144
        'Saint Kitts and Nevis', 'Saint Lucia', 'Saint Vincent', 'Samoa', 'San Marino', 'Sao Tome and Principe', 'Saudi Arabia', 'Senegal', 'Serbia and Montenegro', 'Seychelles', 'Sierra Leone', 'Singapore', 'Slovakia', 'Slovenia', 'Solomon Islands', 'Somalia', 'South Africa', 'Spain', 'Sri Lanka', 'Sudan', 'Suriname', 'Swaziland', 'Sweden', 'Switzerland', 'Syria',
1145
        'Taiwan', 'Tajikistan', 'Tanzania', 'Thailand', 'Togo', 'Tonga', 'Trinidad and Tobago', 'Tunisia', 'Turkey', 'Turkmenistan', 'Tuvalu',
1146
        'Uganda', 'Ukraine', 'United Arab Emirates', 'United Kingdom', 'United States', 'Uruguay', 'Uzbekistan',
1147
        'Vanuatu', 'Vatican City', 'Venezuela', 'Vietnam',
1148
        'Yemen',
1149
        'Zambia', 'Zimbabwe',
1150
    ];
1151
    $options = array_combine($a_countries, $a_countries);
1152
    if ($combo) {
1153
        return Display::select(
1154
            'country',
1155
            $options + ['' => get_lang('Select one')],
1156
            '',
1157
            ['id' => 'country'],
1158
            false
1159
        );
1160
    }
1161
1162
    return $a_countries;
1163
}
1164
1165
/**
1166
 * Lock settings that can't be changed in other portals.
1167
 */
1168
function lockSettings()
1169
{
1170
    $settings = api_get_locked_settings();
1171
    $table = Database::get_main_table(TABLE_MAIN_SETTINGS);
1172
    foreach ($settings as $setting) {
1173
        $sql = "UPDATE $table SET access_url_locked = 1 WHERE variable  = '$setting'";
1174
        Database::query($sql);
1175
    }
1176
}
1177
1178
/**
1179
 * Update dir values.
1180
 */
1181
function updateDirAndFilesPermissions()
1182
{
1183
    $table = Database::get_main_table(TABLE_MAIN_SETTINGS);
1184
    $permissions_for_new_directories = isset($_SESSION['permissions_for_new_directories']) ? $_SESSION['permissions_for_new_directories'] : 0770;
1185
    $permissions_for_new_files = isset($_SESSION['permissions_for_new_files']) ? $_SESSION['permissions_for_new_files'] : 0660;
1186
    // use decoct() to store as string
1187
    Database::update(
1188
        $table,
1189
        ['selected_value' => '0'.decoct($permissions_for_new_directories)],
1190
        ['variable = ?' => 'permissions_for_new_directories']
1191
    );
1192
1193
    Database::update(
1194
        $table,
1195
        ['selected_value' => '0'.decoct($permissions_for_new_files)],
1196
        ['variable = ?' => 'permissions_for_new_files']
1197
    );
1198
1199
    if (isset($_SESSION['permissions_for_new_directories'])) {
1200
        unset($_SESSION['permissions_for_new_directories']);
1201
    }
1202
1203
    if (isset($_SESSION['permissions_for_new_files'])) {
1204
        unset($_SESSION['permissions_for_new_files']);
1205
    }
1206
}
1207
1208
function compare_setting_values(string $current_value, string $wanted_value): array
1209
{
1210
    $tail = substr($current_value, -1, 1);
1211
    $current_value_string = $current_value;
1212
    switch ($tail) {
1213
        case 'T':
1214
            $current_value = ((float) substr($current_value, 0, -1)) * 1024 * 1024;
1215
            break;
1216
        case 'G':
1217
            $current_value = ((float) substr($current_value, 0, -1)) * 1024;
1218
            break;
1219
        case 'M':
1220
        default:
1221
            $current_value = (float) $current_value;
1222
        break;
1223
    }
1224
    $wanted_value = (float) $wanted_value;
1225
1226
    return $current_value >= $wanted_value
1227
        ? ['severity' => 'success', 'value' => $current_value_string]
1228
        : ['severity' => 'danger', 'value' => $current_value_string];
1229
}
1230
1231
/**
1232
 * Save settings values.
1233
 *
1234
 * @param string $organizationName
1235
 * @param string $organizationUrl
1236
 * @param string $siteName
1237
 * @param string $adminEmail
1238
 * @param string $adminLastName
1239
 * @param string $adminFirstName
1240
 * @param string $language
1241
 * @param string $allowRegistration
1242
 * @param string $allowTeacherSelfRegistration
1243
 * @param string $installationProfile          The name of an installation profile file in main/install/profiles/
1244
 */
1245
function installSettings(
1246
    $organizationName,
1247
    $organizationUrl,
1248
    $siteName,
1249
    $adminEmail,
1250
    $adminLastName,
1251
    $adminFirstName,
1252
    $language,
1253
    $allowRegistration,
1254
    $allowTeacherSelfRegistration,
1255
    $installationProfile = '',
1256
    $mailerDsn = '',
1257
    $mailerFromEmail = '',
1258
    $mailerFromName = '',
1259
) {
1260
    error_log('installSettings');
1261
    $allowTeacherSelfRegistration = $allowTeacherSelfRegistration ? 'true' : 'false';
1262
1263
    $settings = [
1264
        'institution' => $organizationName,
1265
        'institution_url' => $organizationUrl,
1266
        'site_name' => $siteName,
1267
        'administrator_email' => $adminEmail,
1268
        'administrator_surname' => $adminLastName,
1269
        'administrator_name' => $adminFirstName,
1270
        'platform_language' => $language,
1271
        'allow_registration' => $allowRegistration,
1272
        'allow_registration_as_teacher' => $allowTeacherSelfRegistration,
1273
        'mailer_dsn' => $mailerDsn,
1274
        'mailer_from_email' => $mailerFromEmail,
1275
        'mailer_from_name' => $mailerFromName,
1276
    ];
1277
1278
    foreach ($settings as $variable => $value) {
1279
        $sql = "UPDATE settings
1280
                SET selected_value = '$value'
1281
                WHERE variable = '$variable'";
1282
        Database::query($sql);
1283
    }
1284
    installProfileSettings($installationProfile);
1285
}
1286
1287
/**
1288
 * Executes DB changes based in the classes defined in
1289
 * /src/CoreBundle/Migrations/Schema/V200/*.
1290
 *
1291
 * @return bool
1292
 */
1293
function migrate(EntityManager $manager)
1294
{
1295
    $debug = true;
1296
    $connection = $manager->getConnection();
1297
    $to = null; // if $to == null then schema will be migrated to latest version
1298
1299
    // Loading migration configuration.
1300
    $config = new PhpFile('./migrations.php');
1301
    $dependency = DependencyFactory::fromConnection($config, new ExistingConnection($connection));
1302
1303
    // Check if old "version" table exists from 1.11.x, use new version.
1304
    $schema = $manager->getConnection()->getSchemaManager();
1305
    $dropOldVersionTable = false;
1306
    if ($schema->tablesExist('version')) {
1307
        $columns = $schema->listTableColumns('version');
1308
        if (in_array('id', array_keys($columns), true)) {
1309
            $dropOldVersionTable = true;
1310
        }
1311
    }
1312
1313
    if ($dropOldVersionTable) {
1314
        error_log('Drop version table');
1315
        $schema->dropTable('version');
1316
    }
1317
1318
    // Creates "version" table.
1319
    $dependency->getMetadataStorage()->ensureInitialized();
1320
1321
    // Loading migrations.
1322
    $migratorConfigurationFactory = $dependency->getConsoleInputMigratorConfigurationFactory();
1323
    $result = '';
1324
    $input = new Symfony\Component\Console\Input\StringInput($result);
1325
    $migratorConfiguration = $migratorConfigurationFactory->getMigratorConfiguration($input);
1326
    $migrator = $dependency->getMigrator();
1327
    $planCalculator = $dependency->getMigrationPlanCalculator();
1328
    $migrations = $planCalculator->getMigrations();
1329
    $lastVersion = $migrations->getLast();
1330
1331
    $plan = $dependency->getMigrationPlanCalculator()->getPlanUntilVersion($lastVersion->getVersion());
1332
1333
    foreach ($plan->getItems() as $item) {
1334
        error_log("Version to be executed: ".$item->getVersion());
1335
        $item->getMigration()->setEntityManager($manager);
1336
        $item->getMigration()->setContainer(Container::$container);
1337
    }
1338
1339
    // Execute migration!!
1340
    /** @var $migratedVersions */
1341
    $versions = $migrator->migrate($plan, $migratorConfiguration);
1342
1343
    if ($debug) {
1344
        /** @var Query[] $queries */
1345
        $versionCounter = 1;
1346
        foreach ($versions as $version => $queries) {
1347
            $total = count($queries);
1348
            //echo '----------------------------------------------<br />';
1349
            $message = "VERSION: $version";
1350
            //echo "$message<br/>";
1351
            error_log('-------------------------------------');
1352
            error_log($message);
1353
            $counter = 1;
1354
            foreach ($queries as $query) {
1355
                $sql = $query->getStatement();
1356
                //echo "<code>$sql</code><br>";
1357
                error_log("$counter/$total : $sql");
1358
                $counter++;
1359
            }
1360
            $versionCounter++;
1361
        }
1362
        //echo '<br/>DONE!<br />';
1363
        error_log('DONE!');
1364
    }
1365
1366
    return true;
1367
}
1368
1369
/**
1370
 * @param string $distFile
1371
 * @param string $envFile
1372
 * @param array  $params
1373
 */
1374
function updateEnvFile($distFile, $envFile, $params)
1375
{
1376
    $requirements = [
1377
        'DATABASE_HOST',
1378
        'DATABASE_PORT',
1379
        'DATABASE_NAME',
1380
        'DATABASE_USER',
1381
        'DATABASE_PASSWORD',
1382
        'APP_INSTALLED',
1383
        'APP_ENCRYPT_METHOD',
1384
        'APP_SECRET',
1385
        'DB_MANAGER_ENABLED',
1386
        'SOFTWARE_NAME',
1387
        'SOFTWARE_URL',
1388
        'DENY_DELETE_USERS',
1389
        'HOSTING_TOTAL_SIZE_LIMIT',
1390
        'THEME_FALLBACK',
1391
        'PACKAGER',
1392
        'DEFAULT_TEMPLATE',
1393
        'ADMIN_CHAMILO_ANNOUNCEMENTS_DISABLE',
1394
    ];
1395
1396
    foreach ($requirements as $requirement) {
1397
        if (!isset($params['{{'.$requirement.'}}'])) {
1398
            throw new \Exception("The parameter $requirement is needed in order to edit the .env file");
1399
        }
1400
    }
1401
1402
    $contents = file_get_contents($distFile);
1403
    $contents = str_replace(array_keys($params), array_values($params), $contents);
1404
    file_put_contents($envFile, $contents);
1405
    error_log("File env saved here: $envFile");
1406
}
1407
1408
function installTools($container, $manager, $upgrade = false)
1409
{
1410
    error_log('installTools');
1411
    // Install course tools (table "tool")
1412
    /** @var ToolChain $toolChain */
1413
    $toolChain = $container->get(ToolChain::class);
1414
    $toolChain->createTools();
1415
}
1416
1417
/**
1418
 * @param SymfonyContainer $container
1419
 * @param bool             $upgrade
1420
 */
1421
function installSchemas($container, $upgrade = false)
1422
{
1423
    error_log('installSchemas');
1424
    $settingsManager = $container->get(Chamilo\CoreBundle\Settings\SettingsManager::class);
1425
1426
    $urlRepo = $container->get(AccessUrlRepository::class);
1427
    $accessUrl = $urlRepo->find(1);
1428
    if (null === $accessUrl) {
1429
        $em = Database::getManager();
1430
1431
        // Creating AccessUrl.
1432
        $accessUrl = new AccessUrl();
1433
        $accessUrl
1434
            ->setUrl(AccessUrl::DEFAULT_ACCESS_URL)
1435
            ->setDescription('')
1436
            ->setActive(1)
1437
            ->setCreatedBy(1)
1438
        ;
1439
        $em->persist($accessUrl);
1440
        $em->flush();
1441
1442
        error_log('AccessUrl created');
1443
    }
1444
1445
    if ($upgrade) {
1446
        error_log('Upgrade settings');
1447
        $settingsManager->updateSchemas($accessUrl);
1448
    } else {
1449
        error_log('Install settings');
1450
        // Installing schemas (filling settings table)
1451
        $settingsManager->installSchemas($accessUrl);
1452
    }
1453
}
1454
1455
/**
1456
 * @param SymfonyContainer $container
1457
 */
1458
function upgradeWithContainer($container)
1459
{
1460
    Container::setContainer($container);
1461
    Container::setLegacyServices($container);
1462
    error_log('setLegacyServices');
1463
    $manager = Database::getManager();
1464
1465
    /** @var GroupRepository $repo */
1466
    $repo = $container->get(GroupRepository::class);
1467
    $repo->createDefaultGroups();
1468
1469
    // @todo check if adminId = 1
1470
    installTools($container, $manager, true);
1471
    installSchemas($container, true);
1472
}
1473
1474
/**
1475
 * After the schema was created (table creation), the function adds
1476
 * admin/platform information.
1477
 *
1478
 * @param \Psr\Container\ContainerInterface $container
1479
 * @param string                            $sysPath
1480
 * @param string                            $encryptPassForm
1481
 * @param string                            $passForm
1482
 * @param string                            $adminLastName
1483
 * @param string                            $adminFirstName
1484
 * @param string                            $loginForm
1485
 * @param string                            $emailForm
1486
 * @param string                            $adminPhoneForm
1487
 * @param string                            $languageForm
1488
 * @param string                            $institutionForm
1489
 * @param string                            $institutionUrlForm
1490
 * @param string                            $siteName
1491
 * @param string                            $allowSelfReg
1492
 * @param string                            $allowSelfRegProf
1493
 * @param string                            $installationProfile Installation profile, if any was provided
1494
 */
1495
function finishInstallationWithContainer(
1496
    $container,
1497
    $sysPath,
1498
    $encryptPassForm,
1499
    $passForm,
1500
    $adminLastName,
1501
    $adminFirstName,
1502
    $loginForm,
1503
    $emailForm,
1504
    $adminPhoneForm,
1505
    $languageForm,
1506
    $institutionForm,
1507
    $institutionUrlForm,
1508
    $siteName,
1509
    $allowSelfReg,
1510
    $allowSelfRegProf,
1511
    $installationProfile = '',
1512
    $mailerDsn,
1513
    $mailerFromEmail,
1514
    $mailerFromName,
1515
    \Chamilo\Kernel $kernel
1516
) {
1517
    Container::setContainer($container);
1518
    Container::setLegacyServices($container);
1519
1520
    $timezone = api_get_timezone();
1521
1522
    $repo = Container::getUserRepository();
1523
    /** @var User $admin */
1524
    $admin = $repo->findOneBy(['username' => 'admin']);
1525
1526
    $em = Container::getEntityManager();
1527
    $accessUrlRepo = $em->getRepository(AccessUrl::class);
1528
1529
    $accessUrl = $accessUrlRepo->findOneBy([]);
1530
1531
    if (!$accessUrl) {
1532
        $accessUrl = Container::getAccessUrlUtil()->getCurrent();
1533
        $em->persist($accessUrl);
1534
        $em->flush();
1535
    }
1536
1537
    $admin
1538
        ->setLastname($adminLastName)
1539
        ->setFirstname($adminFirstName)
1540
        ->setUsername($loginForm)
1541
        ->setStatus(1)
1542
        ->setPlainPassword($passForm)
1543
        ->setEmail($emailForm)
1544
        ->setOfficialCode('ADMIN')
1545
        ->addAuthSourceByAuthentication(UserAuthSource::PLATFORM, $accessUrl)
1546
        ->setPhone($adminPhoneForm)
1547
        ->setLocale($languageForm)
1548
        ->setTimezone($timezone)
1549
    ;
1550
1551
    $existingRoles = method_exists($admin, 'getRoles') ? $admin->getRoles() : [];
1552
    $normalized = array_map('api_normalize_role_code', $existingRoles);
1553
    $mustHave = ['ROLE_ADMIN', 'ROLE_GLOBAL_ADMIN'];
1554
    $roles = array_values(array_unique(array_merge($normalized, $mustHave)));
1555
1556
    $admin->setRoles($roles);
1557
    $repo->updateUser($admin);
1558
1559
    if (in_array('ROLE_ADMIN', $roles, true) || in_array('ROLE_GLOBAL_ADMIN', $roles, true)) {
1560
        UserManager::addUserAsAdmin($admin);
1561
    } else {
1562
        UserManager::removeUserAdmin($admin);
1563
    }
1564
1565
    /** @var User $anonUser */
1566
    $anonUser = $repo->findOneBy(['username' => 'anon']);
1567
    if ($anonUser) {
1568
        $anonUser->addAuthSourceByAuthentication(UserAuthSource::PLATFORM, $accessUrl);
1569
        $anonRoles = method_exists($anonUser, 'getRoles') ? (array) $anonUser->getRoles() : [];
1570
        if (empty($anonRoles)) {
1571
            $anonUser->setRoles(['ROLE_USER']);
1572
        } else {
1573
            $anonUser->setRoles(array_values(array_unique(array_map('api_normalize_role_code', $anonRoles))));
1574
        }
1575
        $repo->updateUser($anonUser);
1576
    }
1577
1578
    /** @var User $fallbackUser */
1579
    $fallbackUser = $repo->findOneBy(['username' => 'fallback_user']);
1580
    if ($fallbackUser) {
1581
        $fallbackUser->addAuthSourceByAuthentication(UserAuthSource::PLATFORM, $accessUrl);
1582
        $fallbackRoles = method_exists($fallbackUser, 'getRoles') ? $fallbackUser->getRoles() : [];
1583
        if (empty($fallbackRoles)) {
1584
            $fallbackUser->setRoles(['ROLE_USER']);
1585
        } else {
1586
            $fallbackUser->setRoles(array_values(array_unique(array_map('api_normalize_role_code', $fallbackRoles))));
1587
        }
1588
        $repo->updateUser($fallbackUser);
1589
    }
1590
1591
    // Set default language
1592
    Database::update(
1593
        Database::get_main_table(TABLE_MAIN_LANGUAGE),
1594
        ['available' => 1],
1595
        ['english_name = ?' => $languageForm]
1596
    );
1597
1598
    // Install settings
1599
    installSettings(
1600
        $institutionForm,
1601
        $institutionUrlForm,
1602
        $siteName,
1603
        $emailForm,
1604
        $adminLastName,
1605
        $adminFirstName,
1606
        $languageForm,
1607
        $allowSelfReg,
1608
        $allowSelfRegProf,
1609
        $installationProfile,
1610
        $mailerDsn,
1611
        $mailerFromEmail ?: $emailForm,
1612
        $mailerFromName,
1613
    );
1614
    lockSettings();
1615
    updateDirAndFilesPermissions();
1616
    executeLexikKeyPair($kernel);
1617
1618
    createExtraConfigFile();
1619
}
1620
1621
/**
1622
 * Update settings based on installation profile defined in a JSON file.
1623
 *
1624
 * @param string $installationProfile The name of the JSON file in main/install/profiles/ folder
1625
 *
1626
 * @return bool false on failure (no bad consequences anyway, just ignoring profile)
1627
 */
1628
function installProfileSettings($installationProfile = '')
1629
{
1630
    error_log('installProfileSettings');
1631
    if (empty($installationProfile)) {
1632
        return false;
1633
    }
1634
    $jsonPath = api_get_path(SYS_PATH).'main/install/profiles/'.$installationProfile.'.json';
1635
    // Make sure the path to the profile is not hacked
1636
    if (!Security::check_abs_path($jsonPath, api_get_path(SYS_PATH).'main/install/profiles/')) {
1637
        return false;
1638
    }
1639
    if (!is_file($jsonPath)) {
1640
        return false;
1641
    }
1642
    if (!is_readable($jsonPath)) {
1643
        return false;
1644
    }
1645
    if (!function_exists('json_decode')) {
1646
        // The php-json extension is not available. Ignore profile.
1647
        return false;
1648
    }
1649
    $json = file_get_contents($jsonPath);
1650
    $params = json_decode($json);
1651
    if (false === $params or null === $params) {
1652
        return false;
1653
    }
1654
    $settings = $params->params;
1655
    if (!empty($params->parent)) {
1656
        installProfileSettings($params->parent);
1657
    }
1658
1659
    $tblSettings = Database::get_main_table(TABLE_MAIN_SETTINGS);
1660
1661
    foreach ($settings as $id => $param) {
1662
        $conditions = ['variable = ? ' => $param->variable];
1663
1664
        if (!empty($param->subkey)) {
1665
            $conditions['AND subkey = ? '] = $param->subkey;
1666
        }
1667
1668
        Database::update(
1669
            $tblSettings,
1670
            ['selected_value' => $param->selected_value],
1671
            $conditions
1672
        );
1673
    }
1674
1675
    return true;
1676
}
1677
1678
/**
1679
 * Quick function to remove a directory with its subdirectories.
1680
 *
1681
 * @param $dir
1682
 */
1683
function rrmdir($dir)
1684
{
1685
    if (is_dir($dir)) {
1686
        $objects = scandir($dir);
1687
        foreach ($objects as $object) {
1688
            if ('.' != $object && '..' != $object) {
1689
                if ('dir' == filetype($dir.'/'.$object)) {
1690
                    @rrmdir($dir.'/'.$object);
1691
                } else {
1692
                    @unlink($dir.'/'.$object);
1693
                }
1694
            }
1695
        }
1696
        reset($objects);
1697
        rmdir($dir);
1698
    }
1699
}
1700
1701
/**
1702
 * Control the different steps of the migration through a big switch.
1703
 *
1704
 * @param string        $fromVersion
1705
 * @param EntityManager $manager
1706
 * @param bool          $processFiles
1707
 *
1708
 * @return bool Always returns true except if the process is broken
1709
 */
1710
function migrateSwitch($fromVersion, $manager, $processFiles = true)
1711
{
1712
    error_log('-----------------------------------------');
1713
    error_log('Starting migration process from '.$fromVersion.' ('.date('Y-m-d H:i:s').')');
1714
    //echo '<a class="btn btn--secondary" href="javascript:void(0)" id="details_button">'.get_lang('Details').'</a><br />';
1715
    //echo '<div id="details" style="display:none">';
1716
    $connection = $manager->getConnection();
1717
1718
    switch ($fromVersion) {
1719
        case '1.11.0':
1720
        case '1.11.1':
1721
        case '1.11.2':
1722
        case '1.11.4':
1723
        case '1.11.6':
1724
        case '1.11.8':
1725
        case '1.11.10':
1726
        case '1.11.12':
1727
        case '1.11.14':
1728
        case '1.11.16':
1729
            $start = time();
1730
            // Migrate using the migration files located in:
1731
            // /srv/http/chamilo2/src/CoreBundle/Migrations/Schema/V200
1732
            $result = migrate($manager);
1733
            error_log('-----------------------------------------');
1734
1735
            if ($result) {
1736
                error_log('Migrations files were executed ('.date('Y-m-d H:i:s').')');
1737
                $sql = "UPDATE settings SET selected_value = '2.0.0'
1738
                        WHERE variable = 'chamilo_database_version'";
1739
                $connection->executeQuery($sql);
1740
                if ($processFiles) {
1741
                    error_log('Update config files');
1742
                    include __DIR__.'/update-files-1.11.0-2.0.0.inc.php';
1743
                    // Only updates the configuration.inc.php with the new version
1744
                    //include __DIR__.'/update-configuration.inc.php';
1745
                }
1746
                $finish = time();
1747
                $total = round(($finish - $start) / 60);
1748
                error_log('Database migration finished:  ('.date('Y-m-d H:i:s').') took '.$total.' minutes');
1749
            } else {
1750
                error_log('There was an error during running migrations. Check error.log');
1751
                exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
1752
            }
1753
            break;
1754
        default:
1755
            break;
1756
    }
1757
1758
    //echo '</div>';
1759
1760
    return true;
1761
}
1762
1763
/**
1764
 * @return string
1765
 */
1766
function generateRandomToken()
1767
{
1768
    return hash('sha1', uniqid(mt_rand(), true));
1769
}
1770
1771
/**
1772
 * This function checks if the given file can be created or overwritten.
1773
 *
1774
 * @param string $file Full path to a file
1775
 */
1776
function checkCanCreateFile(string $file): bool
1777
{
1778
    if (file_exists($file)) {
1779
        return is_writable($file);
1780
    }
1781
1782
    $write = @file_put_contents($file, '');
1783
1784
    if (false !== $write) {
1785
        unlink($file);
1786
1787
        return true;
1788
    }
1789
1790
    return false;
1791
}
1792
1793
/**
1794
 * Checks if the update option is available.
1795
 *
1796
 * This function checks the APP_INSTALLED environment variable to determine if the application is already installed.
1797
 * If the APP_INSTALLED variable is set to '1', it indicates that an update is available.
1798
 *
1799
 * @return bool True if the application is already installed (APP_INSTALLED='1'), otherwise false.
1800
 */
1801
function isUpdateAvailable(): bool
1802
{
1803
    $dotenv = new Dotenv();
1804
    $envFile = api_get_path(SYMFONY_SYS_PATH) . '.env';
1805
    $dotenv->loadEnv($envFile);
1806
1807
    // Check if APP_INSTALLED is set and equals '1'
1808
    if (isset($_ENV['APP_INSTALLED']) && $_ENV['APP_INSTALLED'] === '1') {
1809
        return true;
1810
    }
1811
1812
    // If APP_INSTALLED is not found or not set to '1', assume the application is not installed
1813
    return false;
1814
}
1815
1816
/**
1817
 * Check the current migration status.
1818
 *
1819
 * This function calculates the progress of the database migration by comparing the number of executed migrations
1820
 * with the total number of migration files available in the system. It also retrieves the latest executed migration version.
1821
 *
1822
 * @return array {
1823
 *     An array containing the following keys:
1824
 *
1825
 *     @type int    $progress_percentage The percentage of migrations that have been executed.
1826
 *     @type string $current_migration   The version of the last executed migration, or null if no migrations have been executed.
1827
 * }
1828
 */
1829
function checkMigrationStatus(): array
1830
{
1831
    Database::setManager(initializeEntityManager());
1832
    $manager = Database::getManager();
1833
    $connection = $manager->getConnection();
1834
1835
    $migrationFiles = glob(__DIR__ . '/../../../src/CoreBundle/Migrations/Schema/V200/Version*.php');
1836
    $totalMigrations = count($migrationFiles);
1837
1838
    $executedMigrations = $connection->createQueryBuilder()
1839
        ->select('COUNT(*) as count')
1840
        ->from('version')
1841
        ->execute()
1842
        ->fetchOne();
1843
1844
    $progress_percentage = 0;
1845
    if ($totalMigrations > 0) {
1846
        $progress_percentage = ($executedMigrations / $totalMigrations) * 100;
1847
    }
1848
1849
    $current_migration = $connection->createQueryBuilder()
1850
        ->select('version')
1851
        ->from('version')
1852
        ->orderBy('executed_at', 'DESC')
1853
        ->setMaxResults(1)
1854
        ->execute()
1855
        ->fetchOne();
1856
1857
    return [
1858
        'progress_percentage' => ceil($progress_percentage),
1859
        'current_migration' => $current_migration,
1860
    ];
1861
}
1862
1863
/**
1864
 * Initializes the EntityManager by loading environment variables and connecting to the database.
1865
 *
1866
 * @return EntityManager The initialized EntityManager
1867
 */
1868
function initializeEntityManager(): EntityManager
1869
{
1870
    $dotenv = new Dotenv();
1871
    $envFile = api_get_path(SYMFONY_SYS_PATH) . '.env';
1872
    $dotenv->loadEnv($envFile);
1873
1874
    connectToDatabase(
1875
        $_ENV['DATABASE_HOST'],
1876
        $_ENV['DATABASE_USER'],
1877
        $_ENV['DATABASE_PASSWORD'],
1878
        $_ENV['DATABASE_NAME'],
1879
        $_ENV['DATABASE_PORT']
1880
    );
1881
1882
    $manager = Database::getManager();
1883
1884
    return $manager;
1885
}
1886
1887
/**
1888
 * Checks if the version table in the database is valid.
1889
 *
1890
 * @param Connection $connection The database connection
1891
 *
1892
 * @return bool True if the version table is valid, false otherwise
1893
 */
1894
function isVersionTableValid($connection): bool
1895
{
1896
    $schema = $connection->createSchemaManager();
1897
    if ($schema->tablesExist('version')) {
1898
        $columns = $schema->listTableColumns('version');
1899
1900
        $requiredColumns = ['version', 'executed_at', 'execution_time'];
1901
        foreach ($requiredColumns as $column) {
1902
            if (!isset($columns[$column])) {
1903
                return false;
1904
            }
1905
        }
1906
1907
        $query = $connection->createQueryBuilder()
1908
            ->select('*')
1909
            ->from('version')
1910
            ->orderBy('executed_at', 'DESC')
1911
            ->setMaxResults(1);
1912
        $result = $query->execute()->fetchAll();
1913
1914
        if (!empty($result)) {
1915
            $latestMigrationDate = new DateTime($result[0]['executed_at']);
1916
            $now = new DateTime();
1917
1918
            if ($latestMigrationDate->diff($now)->days < 1) {
1919
                return true;
1920
            }
1921
        }
1922
    }
1923
1924
    return false;
1925
}
1926
1927
/**
1928
 * Retrieves the last executed migration version from the database.
1929
 *
1930
 * @param Connection $connection The database connection
1931
 *
1932
 * @return string The last executed migration version
1933
 */
1934
function getLastExecutedMigration(Connection $connection): string
1935
{
1936
    $query = $connection->createQueryBuilder()
1937
        ->select('version')
1938
        ->from('version')
1939
        ->orderBy('executed_at', 'DESC')
1940
        ->setMaxResults(1);
1941
    $result = $query->execute()->fetchAssociative();
1942
    return $result['version'] ?? '';
1943
}
1944
1945
1946
/**
1947
 * Executes the database migration and returns the status.
1948
 *
1949
 * @return array The result status of the migration
1950
 */
1951
function executeMigration(): array
1952
{
1953
    $resultStatus = [
1954
        'status' => false,
1955
        'message' => 'Error executing migration.',
1956
        'progress_percentage' => 0,
1957
        'current_migration' => '',
1958
    ];
1959
1960
    Database::setManager(initializeEntityManager());
1961
    $manager = Database::getManager();
1962
    $connection = $manager->getConnection();
1963
1964
    try {
1965
        $config = new PhpFile(api_get_path(SYS_CODE_PATH) . 'install/migrations.php');
1966
        $dependency = DependencyFactory::fromConnection($config, new ExistingConnection($connection));
1967
1968
        if (!isVersionTableValid($connection)) {
1969
            $schema = $connection->createSchemaManager();
1970
            $schema->dropTable('version');
1971
        }
1972
1973
        $dependency->getMetadataStorage()->ensureInitialized();
1974
1975
        $env = $_SERVER['APP_ENV'] ?? 'dev';
1976
        $kernel = new Chamilo\Kernel($env, false);
1977
        $kernel->boot();
1978
1979
        $application = new Application($kernel);
1980
        $application->setAutoExit(false);
1981
1982
        $input = new ArrayInput([
1983
            'command' => 'doctrine:migrations:migrate',
1984
            '--no-interaction' => true,
1985
        ]);
1986
1987
        $output = new BufferedOutput();
1988
        $application->run($input, $output);
1989
1990
        $result = $output->fetch();
1991
1992
        createExtraConfigFile();
1993
1994
        if (strpos($result, '[OK] Successfully migrated to version') !== false) {
1995
            $resultStatus['status'] = true;
1996
            $resultStatus['message'] = 'Migration completed successfully.';
1997
            $resultStatus['progress_percentage'] = 100;
1998
        } else {
1999
            $resultStatus['message'] = 'Migration completed with errors.';
2000
            $resultStatus['progress_percentage'] = 0;
2001
        }
2002
2003
        $resultStatus['current_migration'] = getLastExecutedMigration($connection);
2004
    } catch (Exception $e) {
2005
        $resultStatus['current_migration'] = getLastExecutedMigration($connection);
2006
        $resultStatus['message'] = 'Migration failed: ' . $e->getMessage();
2007
    }
2008
2009
    return $resultStatus;
2010
}
2011
2012
/**
2013
 * @throws Exception
2014
 */
2015
function executeLexikKeyPair(\Chamilo\Kernel $kernel): void
2016
{
2017
    $application = new Application($kernel);
2018
    $application->setAutoExit(false);
2019
2020
    $input = new ArrayInput([
2021
        'command' => 'lexik:jwt:generate-keypair',
2022
    ]);
2023
2024
    $output = new NullOutput();
2025
2026
    $application->run($input, $output);
2027
}
2028
2029
function createExtraConfigFile(): void {
2030
    $files = [
2031
        'authentication',
2032
        'settings_overrides',
2033
        'plugin',
2034
    ];
2035
2036
    $sysPath = api_get_path(SYMFONY_SYS_PATH);
2037
2038
    foreach ($files as $file) {
2039
        $finalFilename = $sysPath."config/$file.yaml";
2040
2041
        if (!file_exists($finalFilename)) {
2042
            $distFilename = $sysPath."config/$file.dist.yaml";
2043
2044
            $contents = file_get_contents($distFilename);
2045
2046
            file_put_contents($finalFilename, $contents);
2047
        }
2048
    }
2049
}
2050