Passed
Pull Request — master (#5711)
by
unknown
09:14
created

writeSystemConfigFile()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 32
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

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