Passed
Push — master ( 6695e4...57b771 )
by
unknown
16:47 queued 08:12
created

finishInstallationWithContainer()   C

Complexity

Conditions 12
Paths 200

Size

Total Lines 124
Code Lines 74

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
cc 12
eloc 74
c 1
b 1
f 0
nc 200
nop 20
dl 0
loc 124
rs 5.4739

How to fix   Long Method    Complexity    Many Parameters   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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'), 'LDAP'),
565
        'url' => 'https://php.net/manual/en/book.ldap.php',
566
        'status' => checkExtension(
567
            'ldap',
568
            get_lang('Yes'),
569
            sprintf(get_lang('%s extension not available'), 'LDAP')
570
        ),
571
    ];
572
    $extensions[] = [
573
        'title' => sprintf(get_lang('%s support'), 'Multibyte string'),
574
        'url' => 'https://php.net/manual/en/book.mbstring.php',
575
        'status' => checkExtension(
576
            'mbstring',
577
            get_lang('Yes'),
578
            sprintf(get_lang('%s extension not available'), 'MBString'),
579
            true
580
        ),
581
    ];
582
    $extensions[] = [
583
        'title' => sprintf(get_lang('%s support'), 'Exif'),
584
        'url' => 'https://php.net/manual/en/book.exif.php',
585
        'status' => checkExtension(
586
            'exif',
587
            get_lang('Yes'),
588
            sprintf(get_lang('%s extension not available'), 'Exif'),
589
            true
590
        ),
591
    ];
592
    $extensions[] = [
593
        'title' => sprintf(get_lang('%s support'), 'Zend OpCache'),
594
        'url' => 'https://php.net/opcache',
595
        'status' => checkExtension(
596
            'Zend OPcache',
597
            get_lang('Yes'),
598
            get_lang('No'),
599
            true,
600
            'opcache.enable'
601
        ),
602
    ];
603
    $extensions[] = [
604
        'title' => sprintf(get_lang('%s support'), 'APCu'),
605
        'url' => 'https://php.net/apcu',
606
        'status' => checkExtension(
607
            'apcu',
608
            get_lang('Yes'),
609
            get_lang('No'),
610
            true,
611
            'apc.enabled'
612
        ),
613
    ];
614
    $extensions[] = [
615
        'title' => sprintf(get_lang('%s support'), 'Iconv'),
616
        'url' => 'https://php.net/manual/en/book.iconv.php',
617
        'status' => checkExtension(
618
            'iconv',
619
            get_lang('Yes'),
620
            get_lang('No'),
621
            true
622
        ),
623
    ];
624
    $extensions[] = [
625
        'title' => sprintf(get_lang('%s support'), 'Xapian'),
626
        'url' => 'https://xapian.org/',
627
        'status' => checkExtension(
628
            'xapian',
629
            get_lang('Yes'),
630
            get_lang('No'),
631
            true
632
        ),
633
    ];
634
635
    // RECOMMENDED SETTINGS
636
    // Note: these are the settings for Joomla, does this also apply for Chamilo?
637
    // Note: also add upload_max_filesize here so that large uploads are possible
638
    $phpIni = [];
639
    $phpIni[] = [
640
        'title' => 'Display Errors',
641
        'url' => 'https://php.net/manual/ref.errorfunc.php#ini.display-errors',
642
        'recommended' => 'OFF',
643
        'current' => checkPhpSetting('display_errors', 'OFF'),
644
    ];
645
    $phpIni[] = [
646
        'title' => 'File Uploads',
647
        'url' => 'https://php.net/manual/ini.core.php#ini.file-uploads',
648
        'recommended' => 'ON',
649
        'current' => checkPhpSetting('file_uploads', 'ON'),
650
    ];
651
    $phpIni[] = [
652
        'title' => 'Session auto start',
653
        'url' => 'https://php.net/manual/ref.session.php#ini.session.auto-start',
654
        'recommended' => 'OFF',
655
        'current' => checkPhpSetting('session.auto_start', 'OFF'),
656
    ];
657
    $phpIni[] = [
658
        'title' => 'Short Open Tag',
659
        'url' => 'https://php.net/manual/ini.core.php#ini.short-open-tag',
660
        'recommended' => 'OFF',
661
        'current' => checkPhpSetting('short_open_tag', 'OFF'),
662
    ];
663
    $phpIni[] = [
664
        'title' => 'Cookie HTTP Only',
665
        'url' => 'https://www.php.net/manual/en/session.configuration.php#ini.session.cookie-httponly',
666
        'recommended' => 'ON',
667
        'current' => checkPhpSetting('session.cookie_httponly', 'ON'),
668
    ];
669
    $phpIni[] = [
670
        'title' => 'Maximum upload file size',
671
        'url' => 'https://php.net/manual/ini.core.php#ini.upload-max-filesize',
672
        'recommended' => '>= '.REQUIRED_MIN_UPLOAD_MAX_FILESIZE.'M',
673
        'current' => compare_setting_values(ini_get('upload_max_filesize'), REQUIRED_MIN_UPLOAD_MAX_FILESIZE),
674
    ];
675
    $phpIni[] = [
676
        'title' => 'Maximum post size',
677
        'url' => 'https://php.net/manual/ini.core.php#ini.post-max-size',
678
        'recommended' => '>= '.REQUIRED_MIN_POST_MAX_SIZE.'M',
679
        'current' => compare_setting_values(ini_get('post_max_size'), REQUIRED_MIN_POST_MAX_SIZE),
680
    ];
681
    $phpIni[] = [
682
        'title' => 'Memory Limit',
683
        'url' => 'https://www.php.net/manual/en/ini.core.php#ini.memory-limit',
684
        'recommended' => '>= '.REQUIRED_MIN_MEMORY_LIMIT.'M',
685
        'current' => compare_setting_values($originalMemoryLimit, REQUIRED_MIN_MEMORY_LIMIT),
686
    ];
687
688
    // DIRECTORY AND FILE PERMISSIONS
689
    $_SESSION['permissions_for_new_directories'] = $_setting['permissions_for_new_directories'] = $dir_perm_verified;
690
    $_SESSION['permissions_for_new_files'] = $_setting['permissions_for_new_files'] = $fil_perm_verified;
691
692
    $dirPerm = '0'.decoct($dir_perm_verified);
693
    $filePerm = '0'.decoct($fil_perm_verified);
694
695
    $pathPermissions = [];
696
697
    if (file_exists(api_get_path(SYS_CODE_PATH).'inc/conf/configuration.php')) {
698
        $pathPermissions[] = [
699
            'requirement' => api_get_path(SYS_CODE_PATH).'inc/conf',
700
            'status' => is_writable(api_get_path(SYS_CODE_PATH).'inc/conf'),
701
        ];
702
    }
703
    $basePath = api_get_path(SYMFONY_SYS_PATH);
704
705
    $pathPermissions[] = [
706
        'item' => $basePath.'var/',
707
        'status' => is_writable($basePath.'var'),
708
    ];
709
    $pathPermissions[] = [
710
        'item' => $basePath.'config/',
711
        'status' => is_writable($basePath.'config'),
712
    ];
713
    $pathPermissions[] = [
714
        'item' => $basePath.'.env',
715
        'status' => checkCanCreateFile($basePath.'.env'),
716
    ];
717
    $pathPermissions[] = [
718
        'item' => get_lang('Permissions for new directories'),
719
        'status' => $dirPerm,
720
    ];
721
    $pathPermissions[] = [
722
        'item' => get_lang('Permissions for new files'),
723
        'status' => $filePerm,
724
    ];
725
726
    $notWritable = [];
727
    $deprecatedToRemove = [];
728
729
    $error = false;
730
731
    if ('update' !== $installType || !empty($updatePath) && !$badUpdatePath) {
732
        // First, attempt to set writing permissions if we don't have them yet
733
        //$perm = api_get_permissions_for_new_directories();
734
        $perm = octdec('0777');
735
        //$perm_file = api_get_permissions_for_new_files();
736
        $perm_file = octdec('0666');
737
738
        if (!$course_test_was_created) {
739
            error_log('Installer: Could not create test course - Make sure permissions are fine.');
740
            $error = true;
741
        }
742
743
        $checked_writable = api_get_path(CONFIGURATION_PATH).'configuration.php';
744
        if (file_exists($checked_writable) && !is_writable($checked_writable)) {
745
            $notWritable[] = $checked_writable;
746
            @chmod($checked_writable, $perm_file);
747
        }
748
749
        // Second, if this fails, report an error
750
        //--> The user would have to adjust the permissions manually
751
        if (count($notWritable) > 0) {
752
            error_log('Installer: At least one needed directory or file is not writeable');
753
            $error = true;
754
        }
755
756
        $deprecated = [
757
            api_get_path(SYS_CODE_PATH).'exercice/',
758
            api_get_path(SYS_CODE_PATH).'newscorm/',
759
            api_get_path(SYS_PLUGIN_PATH).'ticket/',
760
            api_get_path(SYS_PLUGIN_PATH).'skype/',
761
        ];
762
763
        foreach ($deprecated as $deprecatedDirectory) {
764
            if (!is_dir($deprecatedDirectory)) {
765
                continue;
766
            }
767
            $deprecatedToRemove[] = $deprecatedDirectory;
768
        }
769
    }
770
771
    return [
772
        'timezone' => $timezone,
773
        'isVersionPassed' => $isVersionPassed,
774
        'phpVersion' => $phpVersion,
775
        'extensions' => $extensions,
776
        'phpIni' => $phpIni,
777
        'pathPermissions' => $pathPermissions,
778
        'step2_update_6' => isset($_POST['step2_update_6']),
779
        'notWritable' => $notWritable,
780
        'existsConfigurationFile' => false,
781
        'deprecatedToRemove' => $deprecatedToRemove,
782
        'installError' => $error,
783
    ];
784
}
785
786
/**
787
 * Displays the license (GNU GPL) as step 2, with
788
 * - an "I accept" button named step3 to proceed to step 3;
789
 * - a "Back" button named step1 to go back to the first step.
790
 */
791
function display_license_agreement(): array
792
{
793
    $license = api_htmlentities(@file_get_contents(api_get_path(SYMFONY_SYS_PATH).'public/documentation/license.txt'));
794
795
    $activtiesList = [
796
        ['Advertising/Marketing/PR'],
797
        ['Agriculture/Forestry'],
798
        ['Architecture'],
799
        ['Banking/Finance'],
800
        ['Biotech/Pharmaceuticals'],
801
        ['Business Equipment'],
802
        ['Business Services'],
803
        ['Construction'],
804
        ['Consulting/Research'],
805
        ['Education'],
806
        ['Engineering'],
807
        ['Environmental'],
808
        ['Government'],
809
        ['Health Care'],
810
        ['Hospitality/Lodging/Travel'],
811
        ['Insurance'],
812
        ['Legal'],
813
        ['Manufacturing'],
814
        ['Media/Entertainment'],
815
        ['Mortgage'],
816
        ['Non-Profit'],
817
        ['Real Estate'],
818
        ['Restaurant'],
819
        ['Retail'],
820
        ['Shipping/Transportation'],
821
        ['Technology'],
822
        ['Telecommunications'],
823
        ['Other'],
824
    ];
825
826
    $rolesList = [
827
        ['Administration'],
828
        ['CEO/President/ Owner'],
829
        ['CFO'],
830
        ['CIO/CTO'],
831
        ['Consultant'],
832
        ['Customer Service'],
833
        ['Engineer/Programmer'],
834
        ['Facilities/Operations'],
835
        ['Finance/ Accounting Manager'],
836
        ['Finance/ Accounting Staff'],
837
        ['General Manager'],
838
        ['Human Resources'],
839
        ['IS/IT Management'],
840
        ['IS/ IT Staff'],
841
        ['Marketing Manager'],
842
        ['Marketing Staff'],
843
        ['Partner/Principal'],
844
        ['Purchasing Manager'],
845
        ['Sales/ Business Dev. Manager'],
846
        ['Sales/ Business Dev.'],
847
        ['Vice President/Senior Manager'],
848
        ['Other'],
849
    ];
850
851
    $countriesList = array_map(
852
        fn ($country) => [$country],
853
        get_countries_list_from_array()
854
    );
855
856
    $languagesList = [
857
        ['bulgarian', 'Bulgarian'],
858
        ['indonesian', 'Bahasa Indonesia'],
859
        ['bosnian', 'Bosanski'],
860
        ['german', 'Deutsch'],
861
        ['english', 'English'],
862
        ['spanish', 'Spanish'],
863
        ['french', 'Français'],
864
        ['italian', 'Italian'],
865
        ['hungarian', 'Magyar'],
866
        ['dutch', 'Nederlands'],
867
        ['brazilian', 'Português do Brasil'],
868
        ['portuguese', 'Português europeu'],
869
        ['slovenian', 'Slovenčina'],
870
    ];
871
872
    return [
873
        'license' => $license,
874
        'activitiesList' => $activtiesList,
875
        'rolesList' => $rolesList,
876
        'countriesList' => $countriesList,
877
        'languagesList' => $languagesList,
878
    ];
879
}
880
881
/**
882
 * Displays a parameter in a table row.
883
 * Used by the display_database_settings_form function.
884
 *
885
 * @param   string  Type of install
886
 * @param   string  Name of parameter
887
 * @param   string  Field name (in the HTML form)
888
 * @param   string  Field value
889
 * @param   string  Extra notice (to show on the right side)
890
 * @param   bool Whether to display in update mode
891
 * @param   string  Additional attribute for the <tr> element
892
 */
893
function displayDatabaseParameter(
894
    $installType,
895
    $parameterName,
896
    $formFieldName,
897
    $parameterValue,
898
    $extra_notice,
899
    $displayWhenUpdate = true
900
) {
901
    echo "<dt class='col-sm-4'>$parameterName</dt>";
902
    echo '<dd class="col-sm-8">';
903
    if (INSTALL_TYPE_UPDATE == $installType && $displayWhenUpdate) {
904
        echo '<input
905
                type="hidden"
906
                name="'.$formFieldName.'"
907
                id="'.$formFieldName.'"
908
                value="'.api_htmlentities($parameterValue).'" />'.$parameterValue;
909
    } else {
910
        $inputType = 'dbPassForm' === $formFieldName ? 'password' : 'text';
911
        //Slightly limit the length of the database prefix to avoid having to cut down the databases names later on
912
        $maxLength = 'dbPrefixForm' === $formFieldName ? '15' : MAX_FORM_FIELD_LENGTH;
913
        if (INSTALL_TYPE_UPDATE == $installType) {
914
            echo '<input
915
                type="hidden" name="'.$formFieldName.'" id="'.$formFieldName.'"
916
                value="'.api_htmlentities($parameterValue).'" />';
917
            echo api_htmlentities($parameterValue);
918
        } else {
919
            echo '<input
920
                        type="'.$inputType.'"
921
                        class="form-control"
922
                        size="'.DATABASE_FORM_FIELD_DISPLAY_LENGTH.'"
923
                        maxlength="'.$maxLength.'"
924
                        name="'.$formFieldName.'"
925
                        id="'.$formFieldName.'"
926
                        value="'.api_htmlentities($parameterValue).'" />
927
                    '.$extra_notice.'
928
                  ';
929
        }
930
    }
931
    echo '</dd>';
932
}
933
934
/**
935
 * Displays step 3 - a form where the user can enter the installation settings
936
 * regarding the databases - login and password, names, prefixes, single
937
 * or multiple databases, tracking or not...
938
 */
939
function display_database_settings_form(
940
    string $installType,
941
    string $dbHostForm,
942
    string $dbUsernameForm,
943
    string $dbPassForm,
944
    string $dbNameForm,
945
    int $dbPortForm = 3306
946
): array {
947
    if ('update' === $installType) {
948
        $dbHostForm = get_config_param('db_host');
949
        $dbUsernameForm = get_config_param('db_user');
950
        $dbPassForm = get_config_param('db_password');
951
        $dbNameForm = get_config_param('main_database');
952
        $dbPortForm = get_config_param('db_port');
953
    }
954
955
    $databaseExists = false;
956
    $databaseConnectionError = '';
957
    $connectionParams = null;
958
959
    try {
960
        if ('update' === $installType) {
961
            connectToDatabase(
962
                $dbHostForm,
963
                $dbUsernameForm,
964
                $dbPassForm,
965
                $dbNameForm,
966
                $dbPortForm
967
            );
968
969
            $manager = Database::getManager();
970
            $connection = $manager->getConnection();
971
            $connection->connect();
972
            $schemaManager = $connection->getSchemaManager();
973
974
            // Test create/alter/drop table
975
            $table = 'zXxTESTxX_'.mt_rand(0, 1000);
976
            $sql = "CREATE TABLE $table (id INT AUTO_INCREMENT NOT NULL, name varchar(255), PRIMARY KEY(id))";
977
            $connection->executeQuery($sql);
978
            $tableCreationWorks = false;
979
            $tableDropWorks = false;
980
            if ($schemaManager->tablesExist($table)) {
981
                $sql = "ALTER TABLE $table ADD COLUMN name2 varchar(140) ";
982
                $connection->executeQuery($sql);
983
                $schemaManager->dropTable($table);
984
                $tableDropWorks = false === $schemaManager->tablesExist($table);
985
            }
986
        } else {
987
            connectToDatabase(
988
                $dbHostForm,
989
                $dbUsernameForm,
990
                $dbPassForm,
991
                null,
992
                $dbPortForm
993
            );
994
995
            $manager = Database::getManager();
996
            $schemaManager = $manager->getConnection()->createSchemaManager();
997
            $databases = $schemaManager->listDatabases();
998
            $databaseExists = in_array($dbNameForm, $databases);
999
        }
1000
    } catch (Exception $e) {
1001
        $databaseConnectionError = $e->getMessage();
1002
        $manager = null;
1003
    }
1004
1005
    if ($manager && $manager->getConnection()->isConnected()) {
1006
        $connectionParams = $manager->getConnection()->getParams();
1007
    }
1008
1009
    return [
1010
        'dbHostForm' => $dbHostForm,
1011
        'dbPortForm' => $dbPortForm,
1012
        'dbUsernameForm' => $dbUsernameForm,
1013
        'dbPassForm' => $dbPassForm,
1014
        'dbNameForm' => $dbNameForm,
1015
        'examplePassword' => api_generate_password(8, false),
1016
        'dbExists' => $databaseExists,
1017
        'dbConnError' => $databaseConnectionError,
1018
        'connParams' => $connectionParams,
1019
    ];
1020
}
1021
1022
/**
1023
 * Displays a parameter in a table row.
1024
 * Used by the display_configuration_settings_form function.
1025
 *
1026
 * @param string $installType
1027
 * @param string $parameterName
1028
 * @param string $formFieldName
1029
 * @param string $parameterValue
1030
 * @param string $displayWhenUpdate
1031
 *
1032
 * @return string
1033
 */
1034
function display_configuration_parameter(
1035
    $installType,
1036
    $parameterName,
1037
    $formFieldName,
1038
    $parameterValue,
1039
    $displayWhenUpdate = 'true'
1040
) {
1041
    $html = '<div class="form-group row">';
1042
    $html .= '<label class="col-sm-6 p-2 control-label">'.$parameterName.'</label>';
1043
    if (INSTALL_TYPE_UPDATE == $installType && $displayWhenUpdate) {
1044
        $html .= '<input
1045
            type="hidden"
1046
            name="'.$formFieldName.'"
1047
            value="'.api_htmlentities($parameterValue, ENT_QUOTES).'" />'.$parameterValue;
1048
    } else {
1049
        $html .= '<div class="col-sm-6">
1050
                    <input
1051
                        class="form-control"
1052
                        type="text"
1053
                        size="'.FORM_FIELD_DISPLAY_LENGTH.'"
1054
                        maxlength="'.MAX_FORM_FIELD_LENGTH.'"
1055
                        name="'.$formFieldName.'"
1056
                        value="'.api_htmlentities($parameterValue, ENT_QUOTES).'" />
1057
                    '.'</div>';
1058
    }
1059
    $html .= '</div>';
1060
1061
    return $html;
1062
}
1063
1064
/**
1065
 * Displays step 4 of the installation - configuration settings about Chamilo itself.
1066
 */
1067
function display_configuration_settings_form(
1068
    string $installType,
1069
    string $urlForm,
1070
    string $languageForm,
1071
    string $emailForm,
1072
    string $adminFirstName,
1073
    string $adminLastName,
1074
    string $adminPhoneForm,
1075
    string $campusForm,
1076
    string $institutionForm,
1077
    string $institutionUrlForm,
1078
    string $encryptPassForm,
1079
    string $allowSelfReg,
1080
    string $allowSelfRegProf,
1081
    string $loginForm,
1082
    string $passForm
1083
): array {
1084
    if ('update' !== $installType && empty($languageForm)) {
1085
        $languageForm = $_SESSION['install_language'];
1086
    }
1087
1088
    $stepData = [];
1089
1090
    if ('update' === $installType) {
1091
        $stepData['rootWeb'] = get_config_param('root_web');
1092
        $stepData['rootSys'] = get_config_param('root_sys');
1093
        $stepData['systemVersion'] = get_config_param('system_version');
1094
    }
1095
1096
    $stepData['loginForm'] = $loginForm;
1097
    $stepData['passForm'] = $passForm;
1098
    $stepData['adminFirstName'] = $adminFirstName;
1099
    $stepData['adminLastName'] = $adminLastName;
1100
    $stepData['emailForm'] = $emailForm;
1101
    $stepData['adminPhoneForm'] = $adminPhoneForm;
1102
    $stepData['languageForm'] = $languageForm;
1103
    $stepData['urlForm'] = $urlForm;
1104
    $stepData['campusForm'] = $campusForm;
1105
    $stepData['institutionForm'] = $institutionForm;
1106
    $stepData['institutionUrlForm'] = $institutionUrlForm;
1107
    $stepData['encryptPassForm'] = $encryptPassForm;
1108
    $stepData['allowSelfReg'] = $allowSelfReg;
1109
    $stepData['allowSelfRegProf'] = $allowSelfRegProf;
1110
1111
    return $stepData;
1112
}
1113
1114
/**
1115
 * This function return countries list from array (hardcoded).
1116
 *
1117
 * @param bool $combo (Optional) True for returning countries list with select html
1118
 *
1119
 * @return array|string countries list
1120
 */
1121
function get_countries_list_from_array($combo = false)
1122
{
1123
    $a_countries = [
1124
        'Afghanistan', 'Albania', 'Algeria', 'Andorra', 'Angola', 'Antigua and Barbuda', 'Argentina', 'Armenia', 'Australia', 'Austria', 'Azerbaijan',
1125
        'Bahamas', 'Bahrain', 'Bangladesh', 'Barbados', 'Belarus', 'Belgium', 'Belize', 'Benin', 'Bhutan', 'Bolivia', 'Bosnia and Herzegovina', 'Botswana', 'Brazil', 'Brunei', 'Bulgaria', 'Burkina Faso', 'Burundi',
1126
        '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',
1127
        'Denmark', 'Djibouti', 'Dominica', 'Dominican Republic',
1128
        'East Timor (Timor Timur)', 'Ecuador', 'Egypt', 'El Salvador', 'Equatorial Guinea', 'Eritrea', 'Estonia', 'Ethiopia',
1129
        'Fiji', 'Finland', 'France',
1130
        'Gabon', 'Gambia, The', 'Georgia', 'Germany', 'Ghana', 'Greece', 'Grenada', 'Guatemala', 'Guinea', 'Guinea-Bissau', 'Guyana',
1131
        'Haiti', 'Honduras', 'Hungary',
1132
        'Iceland', 'India', 'Indonesia', 'Iran', 'Iraq', 'Ireland', 'Israel', 'Italy',
1133
        'Jamaica', 'Japan', 'Jordan',
1134
        'Kazakhstan', 'Kenya', 'Kiribati', 'Korea, North', 'Korea, South', 'Kuwait', 'Kyrgyzstan',
1135
        'Laos', 'Latvia', 'Lebanon', 'Lesotho', 'Liberia', 'Libya', 'Liechtenstein', 'Lithuania', 'Luxembourg',
1136
        'Macedonia', 'Madagascar', 'Malawi', 'Malaysia', 'Maldives', 'Mali', 'Malta', 'Marshall Islands', 'Mauritania', 'Mauritius', 'Mexico', 'Micronesia', 'Moldova', 'Monaco', 'Mongolia', 'Morocco', 'Mozambique', 'Myanmar',
1137
        'Namibia', 'Nauru', 'Nepa', 'Netherlands', 'New Zealand', 'Nicaragua', 'Niger', 'Nigeria', 'Norway',
1138
        'Oman',
1139
        'Pakistan', 'Palau', 'Panama', 'Papua New Guinea', 'Paraguay', 'Peru', 'Philippines', 'Poland', 'Portugal',
1140
        'Qatar',
1141
        'Romania', 'Russia', 'Rwanda',
1142
        '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',
1143
        'Taiwan', 'Tajikistan', 'Tanzania', 'Thailand', 'Togo', 'Tonga', 'Trinidad and Tobago', 'Tunisia', 'Turkey', 'Turkmenistan', 'Tuvalu',
1144
        'Uganda', 'Ukraine', 'United Arab Emirates', 'United Kingdom', 'United States', 'Uruguay', 'Uzbekistan',
1145
        'Vanuatu', 'Vatican City', 'Venezuela', 'Vietnam',
1146
        'Yemen',
1147
        'Zambia', 'Zimbabwe',
1148
    ];
1149
    $options = array_combine($a_countries, $a_countries);
1150
    if ($combo) {
1151
        return Display::select(
1152
            'country',
1153
            $options + ['' => get_lang('Select one')],
1154
            '',
1155
            ['id' => 'country'],
1156
            false
1157
        );
1158
    }
1159
1160
    return $a_countries;
1161
}
1162
1163
/**
1164
 * Lock settings that can't be changed in other portals.
1165
 */
1166
function lockSettings()
1167
{
1168
    $settings = api_get_locked_settings();
1169
    $table = Database::get_main_table(TABLE_MAIN_SETTINGS);
1170
    foreach ($settings as $setting) {
1171
        $sql = "UPDATE $table SET access_url_locked = 1 WHERE variable  = '$setting'";
1172
        Database::query($sql);
1173
    }
1174
}
1175
1176
/**
1177
 * Update dir values.
1178
 */
1179
function updateDirAndFilesPermissions()
1180
{
1181
    $table = Database::get_main_table(TABLE_MAIN_SETTINGS);
1182
    $permissions_for_new_directories = isset($_SESSION['permissions_for_new_directories']) ? $_SESSION['permissions_for_new_directories'] : 0770;
1183
    $permissions_for_new_files = isset($_SESSION['permissions_for_new_files']) ? $_SESSION['permissions_for_new_files'] : 0660;
1184
    // use decoct() to store as string
1185
    Database::update(
1186
        $table,
1187
        ['selected_value' => '0'.decoct($permissions_for_new_directories)],
1188
        ['variable = ?' => 'permissions_for_new_directories']
1189
    );
1190
1191
    Database::update(
1192
        $table,
1193
        ['selected_value' => '0'.decoct($permissions_for_new_files)],
1194
        ['variable = ?' => 'permissions_for_new_files']
1195
    );
1196
1197
    if (isset($_SESSION['permissions_for_new_directories'])) {
1198
        unset($_SESSION['permissions_for_new_directories']);
1199
    }
1200
1201
    if (isset($_SESSION['permissions_for_new_files'])) {
1202
        unset($_SESSION['permissions_for_new_files']);
1203
    }
1204
}
1205
1206
function compare_setting_values(string $current_value, string $wanted_value): array
1207
{
1208
    $tail = substr($current_value, -1, 1);
1209
    $current_value_string = $current_value;
1210
    switch ($tail) {
1211
        case 'T':
1212
            $current_value = ((float) substr($current_value, 0, -1)) * 1024 * 1024;
1213
            break;
1214
        case 'G':
1215
            $current_value = ((float) substr($current_value, 0, -1)) * 1024;
1216
            break;
1217
        case 'M':
1218
        default:
1219
            $current_value = (float) $current_value;
1220
        break;
1221
    }
1222
    $wanted_value = (float) $wanted_value;
1223
1224
    return $current_value >= $wanted_value
1225
        ? ['severity' => 'success', 'value' => $current_value_string]
1226
        : ['severity' => 'danger', 'value' => $current_value_string];
1227
}
1228
1229
/**
1230
 * Save settings values.
1231
 *
1232
 * @param string $organizationName
1233
 * @param string $organizationUrl
1234
 * @param string $siteName
1235
 * @param string $adminEmail
1236
 * @param string $adminLastName
1237
 * @param string $adminFirstName
1238
 * @param string $language
1239
 * @param string $allowRegistration
1240
 * @param string $allowTeacherSelfRegistration
1241
 * @param string $installationProfile          The name of an installation profile file in main/install/profiles/
1242
 */
1243
function installSettings(
1244
    $organizationName,
1245
    $organizationUrl,
1246
    $siteName,
1247
    $adminEmail,
1248
    $adminLastName,
1249
    $adminFirstName,
1250
    $language,
1251
    $allowRegistration,
1252
    $allowTeacherSelfRegistration,
1253
    $installationProfile = '',
1254
    $mailerDsn = '',
1255
    $mailerFromEmail = '',
1256
    $mailerFromName = '',
1257
) {
1258
    error_log('installSettings');
1259
    $allowTeacherSelfRegistration = $allowTeacherSelfRegistration ? 'true' : 'false';
1260
1261
    $settings = [
1262
        'institution' => $organizationName,
1263
        'institution_url' => $organizationUrl,
1264
        'site_name' => $siteName,
1265
        'administrator_email' => $adminEmail,
1266
        'administrator_surname' => $adminLastName,
1267
        'administrator_name' => $adminFirstName,
1268
        'platform_language' => $language,
1269
        'allow_registration' => $allowRegistration,
1270
        'allow_registration_as_teacher' => $allowTeacherSelfRegistration,
1271
        'mailer_dsn' => $mailerDsn,
1272
        'mailer_from_email' => $mailerFromEmail,
1273
        'mailer_from_name' => $mailerFromName,
1274
    ];
1275
1276
    foreach ($settings as $variable => $value) {
1277
        $sql = "UPDATE settings
1278
                SET selected_value = '$value'
1279
                WHERE variable = '$variable'";
1280
        Database::query($sql);
1281
    }
1282
    installProfileSettings($installationProfile);
1283
}
1284
1285
/**
1286
 * Executes DB changes based in the classes defined in
1287
 * /src/CoreBundle/Migrations/Schema/V200/*.
1288
 *
1289
 * @return bool
1290
 */
1291
function migrate(EntityManager $manager)
1292
{
1293
    $debug = true;
1294
    $connection = $manager->getConnection();
1295
    $to = null; // if $to == null then schema will be migrated to latest version
1296
1297
    // Loading migration configuration.
1298
    $config = new PhpFile('./migrations.php');
1299
    $dependency = DependencyFactory::fromConnection($config, new ExistingConnection($connection));
1300
1301
    // Check if old "version" table exists from 1.11.x, use new version.
1302
    $schema = $manager->getConnection()->getSchemaManager();
1303
    $dropOldVersionTable = false;
1304
    if ($schema->tablesExist('version')) {
1305
        $columns = $schema->listTableColumns('version');
1306
        if (in_array('id', array_keys($columns), true)) {
1307
            $dropOldVersionTable = true;
1308
        }
1309
    }
1310
1311
    if ($dropOldVersionTable) {
1312
        error_log('Drop version table');
1313
        $schema->dropTable('version');
1314
    }
1315
1316
    // Creates "version" table.
1317
    $dependency->getMetadataStorage()->ensureInitialized();
1318
1319
    // Loading migrations.
1320
    $migratorConfigurationFactory = $dependency->getConsoleInputMigratorConfigurationFactory();
1321
    $result = '';
1322
    $input = new Symfony\Component\Console\Input\StringInput($result);
1323
    $migratorConfiguration = $migratorConfigurationFactory->getMigratorConfiguration($input);
1324
    $migrator = $dependency->getMigrator();
1325
    $planCalculator = $dependency->getMigrationPlanCalculator();
1326
    $migrations = $planCalculator->getMigrations();
1327
    $lastVersion = $migrations->getLast();
1328
1329
    $plan = $dependency->getMigrationPlanCalculator()->getPlanUntilVersion($lastVersion->getVersion());
1330
1331
    foreach ($plan->getItems() as $item) {
1332
        error_log("Version to be executed: ".$item->getVersion());
1333
        $item->getMigration()->setEntityManager($manager);
1334
        $item->getMigration()->setContainer(Container::$container);
1335
    }
1336
1337
    // Execute migration!!
1338
    /** @var $migratedVersions */
1339
    $versions = $migrator->migrate($plan, $migratorConfiguration);
1340
1341
    if ($debug) {
1342
        /** @var Query[] $queries */
1343
        $versionCounter = 1;
1344
        foreach ($versions as $version => $queries) {
1345
            $total = count($queries);
1346
            //echo '----------------------------------------------<br />';
1347
            $message = "VERSION: $version";
1348
            //echo "$message<br/>";
1349
            error_log('-------------------------------------');
1350
            error_log($message);
1351
            $counter = 1;
1352
            foreach ($queries as $query) {
1353
                $sql = $query->getStatement();
1354
                //echo "<code>$sql</code><br>";
1355
                error_log("$counter/$total : $sql");
1356
                $counter++;
1357
            }
1358
            $versionCounter++;
1359
        }
1360
        //echo '<br/>DONE!<br />';
1361
        error_log('DONE!');
1362
    }
1363
1364
    return true;
1365
}
1366
1367
/**
1368
 * @param string $distFile
1369
 * @param string $envFile
1370
 * @param array  $params
1371
 */
1372
function updateEnvFile($distFile, $envFile, $params)
1373
{
1374
    $requirements = [
1375
        'DATABASE_HOST',
1376
        'DATABASE_PORT',
1377
        'DATABASE_NAME',
1378
        'DATABASE_USER',
1379
        'DATABASE_PASSWORD',
1380
        'APP_INSTALLED',
1381
        'APP_ENCRYPT_METHOD',
1382
        'APP_SECRET',
1383
        'DB_MANAGER_ENABLED',
1384
        'SOFTWARE_NAME',
1385
        'SOFTWARE_URL',
1386
        'DENY_DELETE_USERS',
1387
        'HOSTING_TOTAL_SIZE_LIMIT',
1388
        'THEME_FALLBACK',
1389
        'PACKAGER',
1390
        'DEFAULT_TEMPLATE',
1391
        'ADMIN_CHAMILO_ANNOUNCEMENTS_DISABLE',
1392
    ];
1393
1394
    foreach ($requirements as $requirement) {
1395
        if (!isset($params['{{'.$requirement.'}}'])) {
1396
            throw new \Exception("The parameter $requirement is needed in order to edit the .env file");
1397
        }
1398
    }
1399
1400
    $contents = file_get_contents($distFile);
1401
    $contents = str_replace(array_keys($params), array_values($params), $contents);
1402
    file_put_contents($envFile, $contents);
1403
    error_log("File env saved here: $envFile");
1404
}
1405
1406
function installTools($container, $manager, $upgrade = false)
1407
{
1408
    error_log('installTools');
1409
    // Install course tools (table "tool")
1410
    /** @var ToolChain $toolChain */
1411
    $toolChain = $container->get(ToolChain::class);
1412
    $toolChain->createTools();
1413
}
1414
1415
/**
1416
 * @param SymfonyContainer $container
1417
 * @param bool             $upgrade
1418
 */
1419
function installSchemas($container, $upgrade = false)
1420
{
1421
    error_log('installSchemas');
1422
    $settingsManager = $container->get(Chamilo\CoreBundle\Settings\SettingsManager::class);
1423
1424
    $urlRepo = $container->get(AccessUrlRepository::class);
1425
    $accessUrl = $urlRepo->find(1);
1426
    if (null === $accessUrl) {
1427
        $em = Database::getManager();
1428
1429
        // Creating AccessUrl.
1430
        $accessUrl = new AccessUrl();
1431
        $accessUrl
1432
            ->setUrl(AccessUrl::DEFAULT_ACCESS_URL)
1433
            ->setDescription('')
1434
            ->setActive(1)
1435
            ->setCreatedBy(1)
1436
        ;
1437
        $em->persist($accessUrl);
1438
        $em->flush();
1439
1440
        error_log('AccessUrl created');
1441
    }
1442
1443
    if ($upgrade) {
1444
        error_log('Upgrade settings');
1445
        $settingsManager->updateSchemas($accessUrl);
1446
    } else {
1447
        error_log('Install settings');
1448
        // Installing schemas (filling settings table)
1449
        $settingsManager->installSchemas($accessUrl);
1450
    }
1451
}
1452
1453
/**
1454
 * @param SymfonyContainer $container
1455
 */
1456
function upgradeWithContainer($container)
1457
{
1458
    Container::setContainer($container);
1459
    Container::setLegacyServices($container);
1460
    error_log('setLegacyServices');
1461
    $manager = Database::getManager();
1462
1463
    /** @var GroupRepository $repo */
1464
    $repo = $container->get(GroupRepository::class);
1465
    $repo->createDefaultGroups();
1466
1467
    // @todo check if adminId = 1
1468
    installTools($container, $manager, true);
1469
    installSchemas($container, true);
1470
}
1471
1472
/**
1473
 * After the schema was created (table creation), the function adds
1474
 * admin/platform information.
1475
 *
1476
 * @param \Psr\Container\ContainerInterface $container
1477
 * @param string                            $sysPath
1478
 * @param string                            $encryptPassForm
1479
 * @param string                            $passForm
1480
 * @param string                            $adminLastName
1481
 * @param string                            $adminFirstName
1482
 * @param string                            $loginForm
1483
 * @param string                            $emailForm
1484
 * @param string                            $adminPhoneForm
1485
 * @param string                            $languageForm
1486
 * @param string                            $institutionForm
1487
 * @param string                            $institutionUrlForm
1488
 * @param string                            $siteName
1489
 * @param string                            $allowSelfReg
1490
 * @param string                            $allowSelfRegProf
1491
 * @param string                            $installationProfile Installation profile, if any was provided
1492
 */
1493
function finishInstallationWithContainer(
1494
    $container,
1495
    $sysPath,
1496
    $encryptPassForm,
1497
    $passForm,
1498
    $adminLastName,
1499
    $adminFirstName,
1500
    $loginForm,
1501
    $emailForm,
1502
    $adminPhoneForm,
1503
    $languageForm,
1504
    $institutionForm,
1505
    $institutionUrlForm,
1506
    $siteName,
1507
    $allowSelfReg,
1508
    $allowSelfRegProf,
1509
    $installationProfile = '',
1510
    $mailerDsn,
1511
    $mailerFromEmail,
1512
    $mailerFromName,
1513
    \Chamilo\Kernel $kernel
1514
) {
1515
    Container::setContainer($container);
1516
    Container::setLegacyServices($container);
1517
1518
    $timezone = api_get_timezone();
1519
1520
    $repo = Container::getUserRepository();
1521
    /** @var User $admin */
1522
    $admin = $repo->findOneBy(['username' => 'admin']);
1523
1524
    $em = Container::getEntityManager();
1525
    $accessUrlRepo = $em->getRepository(AccessUrl::class);
1526
1527
    $accessUrl = $accessUrlRepo->findOneBy([]);
1528
1529
    if (!$accessUrl) {
1530
        $accessUrl = Container::getAccessUrlUtil()->getCurrent();
1531
        $em->persist($accessUrl);
1532
        $em->flush();
1533
    }
1534
1535
    $admin
1536
        ->setLastname($adminLastName)
1537
        ->setFirstname($adminFirstName)
1538
        ->setUsername($loginForm)
1539
        ->setStatus(1)
1540
        ->setPlainPassword($passForm)
1541
        ->setEmail($emailForm)
1542
        ->setOfficialCode('ADMIN')
1543
        ->addAuthSourceByAuthentication(UserAuthSource::PLATFORM, $accessUrl)
1544
        ->setPhone($adminPhoneForm)
1545
        ->setLocale($languageForm)
1546
        ->setTimezone($timezone)
1547
    ;
1548
1549
    $existingRoles = method_exists($admin, 'getRoles') ? $admin->getRoles() : [];
1550
    $normalized = array_map('api_normalize_role_code', $existingRoles);
1551
    $mustHave = ['ROLE_ADMIN', 'ROLE_GLOBAL_ADMIN'];
1552
    $roles = array_values(array_unique(array_merge($normalized, $mustHave)));
1553
1554
    $admin->setRoles($roles);
1555
    $repo->updateUser($admin);
1556
1557
    if (in_array('ROLE_ADMIN', $roles, true) || in_array('ROLE_GLOBAL_ADMIN', $roles, true)) {
1558
        UserManager::addUserAsAdmin($admin);
1559
    } else {
1560
        UserManager::removeUserAdmin($admin);
1561
    }
1562
1563
    /** @var User $anonUser */
1564
    $anonUser = $repo->findOneBy(['username' => 'anon']);
1565
    if ($anonUser) {
1566
        $anonUser->addAuthSourceByAuthentication(UserAuthSource::PLATFORM, $accessUrl);
1567
        $anonRoles = method_exists($anonUser, 'getRoles') ? (array) $anonUser->getRoles() : [];
1568
        if (empty($anonRoles)) {
1569
            $anonUser->setRoles(['ROLE_USER']);
1570
        } else {
1571
            $anonUser->setRoles(array_values(array_unique(array_map('api_normalize_role_code', $anonRoles))));
1572
        }
1573
        $repo->updateUser($anonUser);
1574
    }
1575
1576
    /** @var User $fallbackUser */
1577
    $fallbackUser = $repo->findOneBy(['username' => 'fallback_user']);
1578
    if ($fallbackUser) {
1579
        $fallbackUser->addAuthSourceByAuthentication(UserAuthSource::PLATFORM, $accessUrl);
1580
        $fallbackRoles = method_exists($fallbackUser, 'getRoles') ? $fallbackUser->getRoles() : [];
1581
        if (empty($fallbackRoles)) {
1582
            $fallbackUser->setRoles(['ROLE_USER']);
1583
        } else {
1584
            $fallbackUser->setRoles(array_values(array_unique(array_map('api_normalize_role_code', $fallbackRoles))));
1585
        }
1586
        $repo->updateUser($fallbackUser);
1587
    }
1588
1589
    // Set default language
1590
    Database::update(
1591
        Database::get_main_table(TABLE_MAIN_LANGUAGE),
1592
        ['available' => 1],
1593
        ['english_name = ?' => $languageForm]
1594
    );
1595
1596
    // Install settings
1597
    installSettings(
1598
        $institutionForm,
1599
        $institutionUrlForm,
1600
        $siteName,
1601
        $emailForm,
1602
        $adminLastName,
1603
        $adminFirstName,
1604
        $languageForm,
1605
        $allowSelfReg,
1606
        $allowSelfRegProf,
1607
        $installationProfile,
1608
        $mailerDsn,
1609
        $mailerFromEmail ?: $emailForm,
1610
        $mailerFromName,
1611
    );
1612
    lockSettings();
1613
    updateDirAndFilesPermissions();
1614
    executeLexikKeyPair($kernel);
1615
1616
    createExtraConfigFile();
1617
}
1618
1619
/**
1620
 * Update settings based on installation profile defined in a JSON file.
1621
 *
1622
 * @param string $installationProfile The name of the JSON file in main/install/profiles/ folder
1623
 *
1624
 * @return bool false on failure (no bad consequences anyway, just ignoring profile)
1625
 */
1626
function installProfileSettings($installationProfile = '')
1627
{
1628
    error_log('installProfileSettings');
1629
    if (empty($installationProfile)) {
1630
        return false;
1631
    }
1632
    $jsonPath = api_get_path(SYS_PATH).'main/install/profiles/'.$installationProfile.'.json';
1633
    // Make sure the path to the profile is not hacked
1634
    if (!Security::check_abs_path($jsonPath, api_get_path(SYS_PATH).'main/install/profiles/')) {
1635
        return false;
1636
    }
1637
    if (!is_file($jsonPath)) {
1638
        return false;
1639
    }
1640
    if (!is_readable($jsonPath)) {
1641
        return false;
1642
    }
1643
    if (!function_exists('json_decode')) {
1644
        // The php-json extension is not available. Ignore profile.
1645
        return false;
1646
    }
1647
    $json = file_get_contents($jsonPath);
1648
    $params = json_decode($json);
1649
    if (false === $params or null === $params) {
1650
        return false;
1651
    }
1652
    $settings = $params->params;
1653
    if (!empty($params->parent)) {
1654
        installProfileSettings($params->parent);
1655
    }
1656
1657
    $tblSettings = Database::get_main_table(TABLE_MAIN_SETTINGS);
1658
1659
    foreach ($settings as $id => $param) {
1660
        $conditions = ['variable = ? ' => $param->variable];
1661
1662
        if (!empty($param->subkey)) {
1663
            $conditions['AND subkey = ? '] = $param->subkey;
1664
        }
1665
1666
        Database::update(
1667
            $tblSettings,
1668
            ['selected_value' => $param->selected_value],
1669
            $conditions
1670
        );
1671
    }
1672
1673
    return true;
1674
}
1675
1676
/**
1677
 * Quick function to remove a directory with its subdirectories.
1678
 *
1679
 * @param $dir
1680
 */
1681
function rrmdir($dir)
1682
{
1683
    if (is_dir($dir)) {
1684
        $objects = scandir($dir);
1685
        foreach ($objects as $object) {
1686
            if ('.' != $object && '..' != $object) {
1687
                if ('dir' == filetype($dir.'/'.$object)) {
1688
                    @rrmdir($dir.'/'.$object);
1689
                } else {
1690
                    @unlink($dir.'/'.$object);
1691
                }
1692
            }
1693
        }
1694
        reset($objects);
1695
        rmdir($dir);
1696
    }
1697
}
1698
1699
/**
1700
 * Control the different steps of the migration through a big switch.
1701
 *
1702
 * @param string        $fromVersion
1703
 * @param EntityManager $manager
1704
 * @param bool          $processFiles
1705
 *
1706
 * @return bool Always returns true except if the process is broken
1707
 */
1708
function migrateSwitch($fromVersion, $manager, $processFiles = true)
1709
{
1710
    error_log('-----------------------------------------');
1711
    error_log('Starting migration process from '.$fromVersion.' ('.date('Y-m-d H:i:s').')');
1712
    //echo '<a class="btn btn--secondary" href="javascript:void(0)" id="details_button">'.get_lang('Details').'</a><br />';
1713
    //echo '<div id="details" style="display:none">';
1714
    $connection = $manager->getConnection();
1715
1716
    switch ($fromVersion) {
1717
        case '1.11.0':
1718
        case '1.11.1':
1719
        case '1.11.2':
1720
        case '1.11.4':
1721
        case '1.11.6':
1722
        case '1.11.8':
1723
        case '1.11.10':
1724
        case '1.11.12':
1725
        case '1.11.14':
1726
        case '1.11.16':
1727
            $start = time();
1728
            // Migrate using the migration files located in:
1729
            // /srv/http/chamilo2/src/CoreBundle/Migrations/Schema/V200
1730
            $result = migrate($manager);
1731
            error_log('-----------------------------------------');
1732
1733
            if ($result) {
1734
                error_log('Migrations files were executed ('.date('Y-m-d H:i:s').')');
1735
                $sql = "UPDATE settings SET selected_value = '2.0.0'
1736
                        WHERE variable = 'chamilo_database_version'";
1737
                $connection->executeQuery($sql);
1738
                if ($processFiles) {
1739
                    error_log('Update config files');
1740
                    include __DIR__.'/update-files-1.11.0-2.0.0.inc.php';
1741
                    // Only updates the configuration.inc.php with the new version
1742
                    //include __DIR__.'/update-configuration.inc.php';
1743
                }
1744
                $finish = time();
1745
                $total = round(($finish - $start) / 60);
1746
                error_log('Database migration finished:  ('.date('Y-m-d H:i:s').') took '.$total.' minutes');
1747
            } else {
1748
                error_log('There was an error during running migrations. Check error.log');
1749
                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...
1750
            }
1751
            break;
1752
        default:
1753
            break;
1754
    }
1755
1756
    //echo '</div>';
1757
1758
    return true;
1759
}
1760
1761
/**
1762
 * @return string
1763
 */
1764
function generateRandomToken()
1765
{
1766
    return hash('sha1', uniqid(mt_rand(), true));
1767
}
1768
1769
/**
1770
 * This function checks if the given file can be created or overwritten.
1771
 *
1772
 * @param string $file Full path to a file
1773
 */
1774
function checkCanCreateFile(string $file): bool
1775
{
1776
    if (file_exists($file)) {
1777
        return is_writable($file);
1778
    }
1779
1780
    $write = @file_put_contents($file, '');
1781
1782
    if (false !== $write) {
1783
        unlink($file);
1784
1785
        return true;
1786
    }
1787
1788
    return false;
1789
}
1790
1791
/**
1792
 * Checks if the update option is available.
1793
 *
1794
 * This function checks the APP_INSTALLED environment variable to determine if the application is already installed.
1795
 * If the APP_INSTALLED variable is set to '1', it indicates that an update is available.
1796
 *
1797
 * @return bool True if the application is already installed (APP_INSTALLED='1'), otherwise false.
1798
 */
1799
function isUpdateAvailable(): bool
1800
{
1801
    $dotenv = new Dotenv();
1802
    $envFile = api_get_path(SYMFONY_SYS_PATH) . '.env';
1803
    $dotenv->loadEnv($envFile);
1804
1805
    // Check if APP_INSTALLED is set and equals '1'
1806
    if (isset($_ENV['APP_INSTALLED']) && $_ENV['APP_INSTALLED'] === '1') {
1807
        return true;
1808
    }
1809
1810
    // If APP_INSTALLED is not found or not set to '1', assume the application is not installed
1811
    return false;
1812
}
1813
1814
/**
1815
 * Check the current migration status.
1816
 *
1817
 * This function calculates the progress of the database migration by comparing the number of executed migrations
1818
 * with the total number of migration files available in the system. It also retrieves the latest executed migration version.
1819
 *
1820
 * @return array {
1821
 *     An array containing the following keys:
1822
 *
1823
 *     @type int    $progress_percentage The percentage of migrations that have been executed.
1824
 *     @type string $current_migration   The version of the last executed migration, or null if no migrations have been executed.
1825
 * }
1826
 */
1827
function checkMigrationStatus(): array
1828
{
1829
    Database::setManager(initializeEntityManager());
1830
    $manager = Database::getManager();
1831
    $connection = $manager->getConnection();
1832
1833
    $migrationFiles = glob(__DIR__ . '/../../../src/CoreBundle/Migrations/Schema/V200/Version*.php');
1834
    $totalMigrations = count($migrationFiles);
1835
1836
    $executedMigrations = $connection->createQueryBuilder()
1837
        ->select('COUNT(*) as count')
1838
        ->from('version')
1839
        ->execute()
1840
        ->fetchOne();
1841
1842
    $progress_percentage = 0;
1843
    if ($totalMigrations > 0) {
1844
        $progress_percentage = ($executedMigrations / $totalMigrations) * 100;
1845
    }
1846
1847
    $current_migration = $connection->createQueryBuilder()
1848
        ->select('version')
1849
        ->from('version')
1850
        ->orderBy('executed_at', 'DESC')
1851
        ->setMaxResults(1)
1852
        ->execute()
1853
        ->fetchOne();
1854
1855
    return [
1856
        'progress_percentage' => ceil($progress_percentage),
1857
        'current_migration' => $current_migration,
1858
    ];
1859
}
1860
1861
/**
1862
 * Initializes the EntityManager by loading environment variables and connecting to the database.
1863
 *
1864
 * @return EntityManager The initialized EntityManager
1865
 */
1866
function initializeEntityManager(): EntityManager
1867
{
1868
    $dotenv = new Dotenv();
1869
    $envFile = api_get_path(SYMFONY_SYS_PATH) . '.env';
1870
    $dotenv->loadEnv($envFile);
1871
1872
    connectToDatabase(
1873
        $_ENV['DATABASE_HOST'],
1874
        $_ENV['DATABASE_USER'],
1875
        $_ENV['DATABASE_PASSWORD'],
1876
        $_ENV['DATABASE_NAME'],
1877
        $_ENV['DATABASE_PORT']
1878
    );
1879
1880
    $manager = Database::getManager();
1881
1882
    return $manager;
1883
}
1884
1885
/**
1886
 * Checks if the version table in the database is valid.
1887
 *
1888
 * @param Connection $connection The database connection
1889
 *
1890
 * @return bool True if the version table is valid, false otherwise
1891
 */
1892
function isVersionTableValid($connection): bool
1893
{
1894
    $schema = $connection->createSchemaManager();
1895
    if ($schema->tablesExist('version')) {
1896
        $columns = $schema->listTableColumns('version');
1897
1898
        $requiredColumns = ['version', 'executed_at', 'execution_time'];
1899
        foreach ($requiredColumns as $column) {
1900
            if (!isset($columns[$column])) {
1901
                return false;
1902
            }
1903
        }
1904
1905
        $query = $connection->createQueryBuilder()
1906
            ->select('*')
1907
            ->from('version')
1908
            ->orderBy('executed_at', 'DESC')
1909
            ->setMaxResults(1);
1910
        $result = $query->execute()->fetchAll();
1911
1912
        if (!empty($result)) {
1913
            $latestMigrationDate = new DateTime($result[0]['executed_at']);
1914
            $now = new DateTime();
1915
1916
            if ($latestMigrationDate->diff($now)->days < 1) {
1917
                return true;
1918
            }
1919
        }
1920
    }
1921
1922
    return false;
1923
}
1924
1925
/**
1926
 * Retrieves the last executed migration version from the database.
1927
 *
1928
 * @param Connection $connection The database connection
1929
 *
1930
 * @return string The last executed migration version
1931
 */
1932
function getLastExecutedMigration(Connection $connection): string
1933
{
1934
    $query = $connection->createQueryBuilder()
1935
        ->select('version')
1936
        ->from('version')
1937
        ->orderBy('executed_at', 'DESC')
1938
        ->setMaxResults(1);
1939
    $result = $query->execute()->fetchAssociative();
1940
    return $result['version'] ?? '';
1941
}
1942
1943
1944
/**
1945
 * Executes the database migration and returns the status.
1946
 *
1947
 * @return array The result status of the migration
1948
 */
1949
function executeMigration(): array
1950
{
1951
    $resultStatus = [
1952
        'status' => false,
1953
        'message' => 'Error executing migration.',
1954
        'progress_percentage' => 0,
1955
        'current_migration' => '',
1956
    ];
1957
1958
    Database::setManager(initializeEntityManager());
1959
    $manager = Database::getManager();
1960
    $connection = $manager->getConnection();
1961
1962
    try {
1963
        $config = new PhpFile(api_get_path(SYS_CODE_PATH) . 'install/migrations.php');
1964
        $dependency = DependencyFactory::fromConnection($config, new ExistingConnection($connection));
1965
1966
        if (!isVersionTableValid($connection)) {
1967
            $schema = $connection->createSchemaManager();
1968
            $schema->dropTable('version');
1969
        }
1970
1971
        $dependency->getMetadataStorage()->ensureInitialized();
1972
1973
        $env = $_SERVER['APP_ENV'] ?? 'dev';
1974
        $kernel = new Chamilo\Kernel($env, false);
1975
        $kernel->boot();
1976
1977
        $application = new Application($kernel);
1978
        $application->setAutoExit(false);
1979
1980
        $input = new ArrayInput([
1981
            'command' => 'doctrine:migrations:migrate',
1982
            '--no-interaction' => true,
1983
        ]);
1984
1985
        $output = new BufferedOutput();
1986
        $application->run($input, $output);
1987
1988
        $result = $output->fetch();
1989
1990
        createExtraConfigFile();
1991
1992
        if (strpos($result, '[OK] Successfully migrated to version') !== false) {
1993
            $resultStatus['status'] = true;
1994
            $resultStatus['message'] = 'Migration completed successfully.';
1995
            $resultStatus['progress_percentage'] = 100;
1996
        } else {
1997
            $resultStatus['message'] = 'Migration completed with errors.';
1998
            $resultStatus['progress_percentage'] = 0;
1999
        }
2000
2001
        $resultStatus['current_migration'] = getLastExecutedMigration($connection);
2002
    } catch (Exception $e) {
2003
        $resultStatus['current_migration'] = getLastExecutedMigration($connection);
2004
        $resultStatus['message'] = 'Migration failed: ' . $e->getMessage();
2005
    }
2006
2007
    return $resultStatus;
2008
}
2009
2010
/**
2011
 * @throws Exception
2012
 */
2013
function executeLexikKeyPair(\Chamilo\Kernel $kernel): void
2014
{
2015
    $application = new Application($kernel);
2016
    $application->setAutoExit(false);
2017
2018
    $input = new ArrayInput([
2019
        'command' => 'lexik:jwt:generate-keypair',
2020
    ]);
2021
2022
    $output = new NullOutput();
2023
2024
    $application->run($input, $output);
2025
}
2026
2027
function createExtraConfigFile(): void {
2028
    $files = [
2029
        'authentication',
2030
        'settings_overrides',
2031
        'plugin',
2032
    ];
2033
2034
    $sysPath = api_get_path(SYMFONY_SYS_PATH);
2035
2036
    foreach ($files as $file) {
2037
        $finalFilename = $sysPath."config/$file.yaml";
2038
2039
        if (!file_exists($finalFilename)) {
2040
            $distFilename = $sysPath."config/$file.dist.yaml";
2041
2042
            $contents = file_get_contents($distFilename);
2043
2044
            file_put_contents($finalFilename, $contents);
2045
        }
2046
    }
2047
}
2048