Passed
Push — master ( 205c67...5b1229 )
by Angel Fernando Quiroz
08:22
created

createExtraConfigFile()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 18
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

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