Passed
Push — master ( c910de...a2879d )
by
unknown
18:31 queued 11:35
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' => get_lang('Permissions for new directories'),
698
        'status' => $dirPerm,
699
    ];
700
    $pathPermissions[] = [
701
        'item' => get_lang('Permissions for new files'),
702
        'status' => $filePerm,
703
    ];
704
705
    $notWritable = [];
706
    $deprecatedToRemove = [];
707
708
    $error = false;
709
710
    if ('update' !== $installType || !empty($updatePath) && !$badUpdatePath) {
711
        // First, attempt to set writing permissions if we don't have them yet
712
        //$perm = api_get_permissions_for_new_directories();
713
        $perm = octdec('0777');
714
        //$perm_file = api_get_permissions_for_new_files();
715
        $perm_file = octdec('0666');
716
717
        if (!$course_test_was_created) {
718
            error_log('Installer: Could not create test course - Make sure permissions are fine.');
719
            $error = true;
720
        }
721
722
        $checked_writable = api_get_path(CONFIGURATION_PATH).'configuration.php';
723
        if (file_exists($checked_writable) && !is_writable($checked_writable)) {
724
            $notWritable[] = $checked_writable;
725
            @chmod($checked_writable, $perm_file);
726
        }
727
728
        // Second, if this fails, report an error
729
        //--> The user would have to adjust the permissions manually
730
        if (count($notWritable) > 0) {
731
            error_log('Installer: At least one needed directory or file is not writeable');
732
            $error = true;
733
        }
734
735
        $deprecated = [
736
            api_get_path(SYS_CODE_PATH).'exercice/',
737
            api_get_path(SYS_CODE_PATH).'newscorm/',
738
            api_get_path(SYS_PLUGIN_PATH).'ticket/',
739
            api_get_path(SYS_PLUGIN_PATH).'skype/',
740
        ];
741
742
        foreach ($deprecated as $deprecatedDirectory) {
743
            if (!is_dir($deprecatedDirectory)) {
744
                continue;
745
            }
746
            $deprecatedToRemove[] = $deprecatedDirectory;
747
        }
748
    }
749
750
    return [
751
        'timezone' => $timezone,
752
        'isVersionPassed' => $isVersionPassed,
753
        'phpVersion' => $phpVersion,
754
        'extensions' => $extensions,
755
        'phpIni' => $phpIni,
756
        'pathPermissions' => $pathPermissions,
757
        'step2_update_6' => isset($_POST['step2_update_6']),
758
        'notWritable' => $notWritable,
759
        'existsConfigurationFile' => false,
760
        'deprecatedToRemove' => $deprecatedToRemove,
761
        'installError' => $error,
762
    ];
763
}
764
765
/**
766
 * Displays the license (GNU GPL) as step 2, with
767
 * - an "I accept" button named step3 to proceed to step 3;
768
 * - a "Back" button named step1 to go back to the first step.
769
 */
770
function display_license_agreement(): array
771
{
772
    $license = api_htmlentities(@file_get_contents(api_get_path(SYMFONY_SYS_PATH).'public/documentation/license.txt'));
773
774
    $activtiesList = [
775
        ['Advertising/Marketing/PR'],
776
        ['Agriculture/Forestry'],
777
        ['Architecture'],
778
        ['Banking/Finance'],
779
        ['Biotech/Pharmaceuticals'],
780
        ['Business Equipment'],
781
        ['Business Services'],
782
        ['Construction'],
783
        ['Consulting/Research'],
784
        ['Education'],
785
        ['Engineering'],
786
        ['Environmental'],
787
        ['Government'],
788
        ['Health Care'],
789
        ['Hospitality/Lodging/Travel'],
790
        ['Insurance'],
791
        ['Legal'],
792
        ['Manufacturing'],
793
        ['Media/Entertainment'],
794
        ['Mortgage'],
795
        ['Non-Profit'],
796
        ['Real Estate'],
797
        ['Restaurant'],
798
        ['Retail'],
799
        ['Shipping/Transportation'],
800
        ['Technology'],
801
        ['Telecommunications'],
802
        ['Other'],
803
    ];
804
805
    $rolesList = [
806
        ['Administration'],
807
        ['CEO/President/ Owner'],
808
        ['CFO'],
809
        ['CIO/CTO'],
810
        ['Consultant'],
811
        ['Customer Service'],
812
        ['Engineer/Programmer'],
813
        ['Facilities/Operations'],
814
        ['Finance/ Accounting Manager'],
815
        ['Finance/ Accounting Staff'],
816
        ['General Manager'],
817
        ['Human Resources'],
818
        ['IS/IT Management'],
819
        ['IS/ IT Staff'],
820
        ['Marketing Manager'],
821
        ['Marketing Staff'],
822
        ['Partner/Principal'],
823
        ['Purchasing Manager'],
824
        ['Sales/ Business Dev. Manager'],
825
        ['Sales/ Business Dev.'],
826
        ['Vice President/Senior Manager'],
827
        ['Other'],
828
    ];
829
830
    $countriesList = array_map(
831
        fn ($country) => [$country],
832
        get_countries_list_from_array()
833
    );
834
835
    $languagesList = [
836
        ['bulgarian', 'Bulgarian'],
837
        ['indonesian', 'Bahasa Indonesia'],
838
        ['bosnian', 'Bosanski'],
839
        ['german', 'Deutsch'],
840
        ['english', 'English'],
841
        ['spanish', 'Spanish'],
842
        ['french', 'Français'],
843
        ['italian', 'Italian'],
844
        ['hungarian', 'Magyar'],
845
        ['dutch', 'Nederlands'],
846
        ['brazilian', 'Português do Brasil'],
847
        ['portuguese', 'Português europeu'],
848
        ['slovenian', 'Slovenčina'],
849
    ];
850
851
    return [
852
        'license' => $license,
853
        'activitiesList' => $activtiesList,
854
        'rolesList' => $rolesList,
855
        'countriesList' => $countriesList,
856
        'languagesList' => $languagesList,
857
    ];
858
}
859
860
/**
861
 * Displays a parameter in a table row.
862
 * Used by the display_database_settings_form function.
863
 *
864
 * @param   string  Type of install
865
 * @param   string  Name of parameter
866
 * @param   string  Field name (in the HTML form)
867
 * @param   string  Field value
868
 * @param   string  Extra notice (to show on the right side)
869
 * @param   bool Whether to display in update mode
870
 * @param   string  Additional attribute for the <tr> element
871
 */
872
function displayDatabaseParameter(
873
    $installType,
874
    $parameterName,
875
    $formFieldName,
876
    $parameterValue,
877
    $extra_notice,
878
    $displayWhenUpdate = true
879
) {
880
    echo "<dt class='col-sm-4'>$parameterName</dt>";
881
    echo '<dd class="col-sm-8">';
882
    if (INSTALL_TYPE_UPDATE == $installType && $displayWhenUpdate) {
883
        echo '<input
884
                type="hidden"
885
                name="'.$formFieldName.'"
886
                id="'.$formFieldName.'"
887
                value="'.api_htmlentities($parameterValue).'" />'.$parameterValue;
888
    } else {
889
        $inputType = 'dbPassForm' === $formFieldName ? 'password' : 'text';
890
        //Slightly limit the length of the database prefix to avoid having to cut down the databases names later on
891
        $maxLength = 'dbPrefixForm' === $formFieldName ? '15' : MAX_FORM_FIELD_LENGTH;
892
        if (INSTALL_TYPE_UPDATE == $installType) {
893
            echo '<input
894
                type="hidden" name="'.$formFieldName.'" id="'.$formFieldName.'"
895
                value="'.api_htmlentities($parameterValue).'" />';
896
            echo api_htmlentities($parameterValue);
897
        } else {
898
            echo '<input
899
                        type="'.$inputType.'"
900
                        class="form-control"
901
                        size="'.DATABASE_FORM_FIELD_DISPLAY_LENGTH.'"
902
                        maxlength="'.$maxLength.'"
903
                        name="'.$formFieldName.'"
904
                        id="'.$formFieldName.'"
905
                        value="'.api_htmlentities($parameterValue).'" />
906
                    '.$extra_notice.'
907
                  ';
908
        }
909
    }
910
    echo '</dd>';
911
}
912
913
/**
914
 * Displays step 3 - a form where the user can enter the installation settings
915
 * regarding the databases - login and password, names, prefixes, single
916
 * or multiple databases, tracking or not...
917
 */
918
function display_database_settings_form(
919
    string $installType,
920
    string $dbHostForm,
921
    string $dbUsernameForm,
922
    string $dbPassForm,
923
    string $dbNameForm,
924
    int $dbPortForm = 3306
925
): array {
926
    if ('update' === $installType) {
927
        $dbHostForm = get_config_param('db_host');
928
        $dbUsernameForm = get_config_param('db_user');
929
        $dbPassForm = get_config_param('db_password');
930
        $dbNameForm = get_config_param('main_database');
931
        $dbPortForm = get_config_param('db_port');
932
    }
933
934
    $databaseExists = false;
935
    $databaseConnectionError = '';
936
    $connectionParams = null;
937
938
    try {
939
        if ('update' === $installType) {
940
            connectToDatabase(
941
                $dbHostForm,
942
                $dbUsernameForm,
943
                $dbPassForm,
944
                $dbNameForm,
945
                $dbPortForm
946
            );
947
948
            $manager = Database::getManager();
949
            $connection = $manager->getConnection();
950
            $connection->connect();
951
            $schemaManager = $connection->getSchemaManager();
952
953
            // Test create/alter/drop table
954
            $table = 'zXxTESTxX_'.mt_rand(0, 1000);
955
            $sql = "CREATE TABLE $table (id INT AUTO_INCREMENT NOT NULL, name varchar(255), PRIMARY KEY(id))";
956
            $connection->executeQuery($sql);
957
            $tableCreationWorks = false;
958
            $tableDropWorks = false;
959
            if ($schemaManager->tablesExist($table)) {
960
                $sql = "ALTER TABLE $table ADD COLUMN name2 varchar(140) ";
961
                $connection->executeQuery($sql);
962
                $schemaManager->dropTable($table);
963
                $tableDropWorks = false === $schemaManager->tablesExist($table);
964
            }
965
        } else {
966
            connectToDatabase(
967
                $dbHostForm,
968
                $dbUsernameForm,
969
                $dbPassForm,
970
                null,
971
                $dbPortForm
972
            );
973
974
            $manager = Database::getManager();
975
            $schemaManager = $manager->getConnection()->createSchemaManager();
976
            $databases = $schemaManager->listDatabases();
977
            $databaseExists = in_array($dbNameForm, $databases);
978
        }
979
    } catch (Exception $e) {
980
        $databaseConnectionError = $e->getMessage();
981
        $manager = null;
982
    }
983
984
    if ($manager && $manager->getConnection()->isConnected()) {
985
        $connectionParams = $manager->getConnection()->getParams();
986
    }
987
988
    return [
989
        'dbHostForm' => $dbHostForm,
990
        'dbPortForm' => $dbPortForm,
991
        'dbUsernameForm' => $dbUsernameForm,
992
        'dbPassForm' => $dbPassForm,
993
        'dbNameForm' => $dbNameForm,
994
        'examplePassword' => api_generate_password(8, false),
995
        'dbExists' => $databaseExists,
996
        'dbConnError' => $databaseConnectionError,
997
        'connParams' => $connectionParams,
998
    ];
999
}
1000
1001
/**
1002
 * Displays a parameter in a table row.
1003
 * Used by the display_configuration_settings_form function.
1004
 *
1005
 * @param string $installType
1006
 * @param string $parameterName
1007
 * @param string $formFieldName
1008
 * @param string $parameterValue
1009
 * @param string $displayWhenUpdate
1010
 *
1011
 * @return string
1012
 */
1013
function display_configuration_parameter(
1014
    $installType,
1015
    $parameterName,
1016
    $formFieldName,
1017
    $parameterValue,
1018
    $displayWhenUpdate = 'true'
1019
) {
1020
    $html = '<div class="form-group row">';
1021
    $html .= '<label class="col-sm-6 p-2 control-label">'.$parameterName.'</label>';
1022
    if (INSTALL_TYPE_UPDATE == $installType && $displayWhenUpdate) {
1023
        $html .= '<input
1024
            type="hidden"
1025
            name="'.$formFieldName.'"
1026
            value="'.api_htmlentities($parameterValue, ENT_QUOTES).'" />'.$parameterValue;
1027
    } else {
1028
        $html .= '<div class="col-sm-6">
1029
                    <input
1030
                        class="form-control"
1031
                        type="text"
1032
                        size="'.FORM_FIELD_DISPLAY_LENGTH.'"
1033
                        maxlength="'.MAX_FORM_FIELD_LENGTH.'"
1034
                        name="'.$formFieldName.'"
1035
                        value="'.api_htmlentities($parameterValue, ENT_QUOTES).'" />
1036
                    '.'</div>';
1037
    }
1038
    $html .= '</div>';
1039
1040
    return $html;
1041
}
1042
1043
/**
1044
 * Displays step 4 of the installation - configuration settings about Chamilo itself.
1045
 */
1046
function display_configuration_settings_form(
1047
    string $installType,
1048
    string $urlForm,
1049
    string $languageForm,
1050
    string $emailForm,
1051
    string $adminFirstName,
1052
    string $adminLastName,
1053
    string $adminPhoneForm,
1054
    string $campusForm,
1055
    string $institutionForm,
1056
    string $institutionUrlForm,
1057
    string $encryptPassForm,
1058
    string $allowSelfReg,
1059
    string $allowSelfRegProf,
1060
    string $loginForm,
1061
    string $passForm
1062
): array {
1063
    if ('update' !== $installType && empty($languageForm)) {
1064
        $languageForm = $_SESSION['install_language'];
1065
    }
1066
1067
    $stepData = [];
1068
1069
    if ('update' === $installType) {
1070
        $stepData['rootWeb'] = get_config_param('root_web');
1071
        $stepData['rootSys'] = get_config_param('root_sys');
1072
        $stepData['systemVersion'] = get_config_param('system_version');
1073
    }
1074
1075
    $stepData['loginForm'] = $loginForm;
1076
    $stepData['passForm'] = $passForm;
1077
    $stepData['adminFirstName'] = $adminFirstName;
1078
    $stepData['adminLastName'] = $adminLastName;
1079
    $stepData['emailForm'] = $emailForm;
1080
    $stepData['adminPhoneForm'] = $adminPhoneForm;
1081
    $stepData['languageForm'] = $languageForm;
1082
    $stepData['urlForm'] = $urlForm;
1083
    $stepData['campusForm'] = $campusForm;
1084
    $stepData['institutionForm'] = $institutionForm;
1085
    $stepData['institutionUrlForm'] = $institutionUrlForm;
1086
    $stepData['encryptPassForm'] = $encryptPassForm;
1087
    $stepData['allowSelfReg'] = $allowSelfReg;
1088
    $stepData['allowSelfRegProf'] = $allowSelfRegProf;
1089
1090
    return $stepData;
1091
}
1092
1093
/**
1094
 * This function return countries list from array (hardcoded).
1095
 *
1096
 * @param bool $combo (Optional) True for returning countries list with select html
1097
 *
1098
 * @return array|string countries list
1099
 */
1100
function get_countries_list_from_array($combo = false)
1101
{
1102
    $a_countries = [
1103
        'Afghanistan', 'Albania', 'Algeria', 'Andorra', 'Angola', 'Antigua and Barbuda', 'Argentina', 'Armenia', 'Australia', 'Austria', 'Azerbaijan',
1104
        'Bahamas', 'Bahrain', 'Bangladesh', 'Barbados', 'Belarus', 'Belgium', 'Belize', 'Benin', 'Bhutan', 'Bolivia', 'Bosnia and Herzegovina', 'Botswana', 'Brazil', 'Brunei', 'Bulgaria', 'Burkina Faso', 'Burundi',
1105
        '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',
1106
        'Denmark', 'Djibouti', 'Dominica', 'Dominican Republic',
1107
        'East Timor (Timor Timur)', 'Ecuador', 'Egypt', 'El Salvador', 'Equatorial Guinea', 'Eritrea', 'Estonia', 'Ethiopia',
1108
        'Fiji', 'Finland', 'France',
1109
        'Gabon', 'Gambia, The', 'Georgia', 'Germany', 'Ghana', 'Greece', 'Grenada', 'Guatemala', 'Guinea', 'Guinea-Bissau', 'Guyana',
1110
        'Haiti', 'Honduras', 'Hungary',
1111
        'Iceland', 'India', 'Indonesia', 'Iran', 'Iraq', 'Ireland', 'Israel', 'Italy',
1112
        'Jamaica', 'Japan', 'Jordan',
1113
        'Kazakhstan', 'Kenya', 'Kiribati', 'Korea, North', 'Korea, South', 'Kuwait', 'Kyrgyzstan',
1114
        'Laos', 'Latvia', 'Lebanon', 'Lesotho', 'Liberia', 'Libya', 'Liechtenstein', 'Lithuania', 'Luxembourg',
1115
        'Macedonia', 'Madagascar', 'Malawi', 'Malaysia', 'Maldives', 'Mali', 'Malta', 'Marshall Islands', 'Mauritania', 'Mauritius', 'Mexico', 'Micronesia', 'Moldova', 'Monaco', 'Mongolia', 'Morocco', 'Mozambique', 'Myanmar',
1116
        'Namibia', 'Nauru', 'Nepa', 'Netherlands', 'New Zealand', 'Nicaragua', 'Niger', 'Nigeria', 'Norway',
1117
        'Oman',
1118
        'Pakistan', 'Palau', 'Panama', 'Papua New Guinea', 'Paraguay', 'Peru', 'Philippines', 'Poland', 'Portugal',
1119
        'Qatar',
1120
        'Romania', 'Russia', 'Rwanda',
1121
        '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',
1122
        'Taiwan', 'Tajikistan', 'Tanzania', 'Thailand', 'Togo', 'Tonga', 'Trinidad and Tobago', 'Tunisia', 'Turkey', 'Turkmenistan', 'Tuvalu',
1123
        'Uganda', 'Ukraine', 'United Arab Emirates', 'United Kingdom', 'United States', 'Uruguay', 'Uzbekistan',
1124
        'Vanuatu', 'Vatican City', 'Venezuela', 'Vietnam',
1125
        'Yemen',
1126
        'Zambia', 'Zimbabwe',
1127
    ];
1128
    $options = array_combine($a_countries, $a_countries);
1129
    if ($combo) {
1130
        return Display::select(
1131
            'country',
1132
            $options + ['' => get_lang('Select one')],
1133
            '',
1134
            ['id' => 'country'],
1135
            false
1136
        );
1137
    }
1138
1139
    return $a_countries;
1140
}
1141
1142
/**
1143
 * Lock settings that can't be changed in other portals.
1144
 */
1145
function lockSettings()
1146
{
1147
    $settings = api_get_locked_settings();
1148
    $table = Database::get_main_table(TABLE_MAIN_SETTINGS);
1149
    foreach ($settings as $setting) {
1150
        $sql = "UPDATE $table SET access_url_locked = 1 WHERE variable  = '$setting'";
1151
        Database::query($sql);
1152
    }
1153
}
1154
1155
/**
1156
 * Update dir values.
1157
 */
1158
function updateDirAndFilesPermissions()
1159
{
1160
    $table = Database::get_main_table(TABLE_MAIN_SETTINGS);
1161
    $permissions_for_new_directories = isset($_SESSION['permissions_for_new_directories']) ? $_SESSION['permissions_for_new_directories'] : 0770;
1162
    $permissions_for_new_files = isset($_SESSION['permissions_for_new_files']) ? $_SESSION['permissions_for_new_files'] : 0660;
1163
    // use decoct() to store as string
1164
    Database::update(
1165
        $table,
1166
        ['selected_value' => '0'.decoct($permissions_for_new_directories)],
1167
        ['variable = ?' => 'permissions_for_new_directories']
1168
    );
1169
1170
    Database::update(
1171
        $table,
1172
        ['selected_value' => '0'.decoct($permissions_for_new_files)],
1173
        ['variable = ?' => 'permissions_for_new_files']
1174
    );
1175
1176
    if (isset($_SESSION['permissions_for_new_directories'])) {
1177
        unset($_SESSION['permissions_for_new_directories']);
1178
    }
1179
1180
    if (isset($_SESSION['permissions_for_new_files'])) {
1181
        unset($_SESSION['permissions_for_new_files']);
1182
    }
1183
}
1184
1185
function compare_setting_values(string $current_value, string $wanted_value): array
1186
{
1187
    $current_value_string = $current_value;
1188
    $current_value = (float) $current_value;
1189
    $wanted_value = (float) $wanted_value;
1190
1191
    return $current_value >= $wanted_value
1192
        ? ['severity' => 'success', 'value' => $current_value_string]
1193
        : ['severity' => 'danger', 'value' => $current_value_string];
1194
}
1195
1196
/**
1197
 * Save settings values.
1198
 *
1199
 * @param string $organizationName
1200
 * @param string $organizationUrl
1201
 * @param string $siteName
1202
 * @param string $adminEmail
1203
 * @param string $adminLastName
1204
 * @param string $adminFirstName
1205
 * @param string $language
1206
 * @param string $allowRegistration
1207
 * @param string $allowTeacherSelfRegistration
1208
 * @param string $installationProfile          The name of an installation profile file in main/install/profiles/
1209
 */
1210
function installSettings(
1211
    $organizationName,
1212
    $organizationUrl,
1213
    $siteName,
1214
    $adminEmail,
1215
    $adminLastName,
1216
    $adminFirstName,
1217
    $language,
1218
    $allowRegistration,
1219
    $allowTeacherSelfRegistration,
1220
    $installationProfile = ''
1221
) {
1222
    error_log('installSettings');
1223
    $allowTeacherSelfRegistration = $allowTeacherSelfRegistration ? 'true' : 'false';
1224
1225
    $settings = [
1226
        'institution' => $organizationName,
1227
        'institution_url' => $organizationUrl,
1228
        'site_name' => $siteName,
1229
        'administrator_email' => $adminEmail,
1230
        'administrator_surname' => $adminLastName,
1231
        'administrator_name' => $adminFirstName,
1232
        'platform_language' => $language,
1233
        'allow_registration' => $allowRegistration,
1234
        'allow_registration_as_teacher' => $allowTeacherSelfRegistration,
1235
    ];
1236
1237
    foreach ($settings as $variable => $value) {
1238
        $sql = "UPDATE settings
1239
                SET selected_value = '$value'
1240
                WHERE variable = '$variable'";
1241
        Database::query($sql);
1242
    }
1243
    installProfileSettings($installationProfile);
1244
}
1245
1246
/**
1247
 * Executes DB changes based in the classes defined in
1248
 * /src/CoreBundle/Migrations/Schema/V200/*.
1249
 *
1250
 * @return bool
1251
 */
1252
function migrate(EntityManager $manager)
1253
{
1254
    $debug = true;
1255
    $connection = $manager->getConnection();
1256
    $to = null; // if $to == null then schema will be migrated to latest version
1257
1258
    // Loading migration configuration.
1259
    $config = new PhpFile('./migrations.php');
1260
    $dependency = DependencyFactory::fromConnection($config, new ExistingConnection($connection));
1261
1262
    // Check if old "version" table exists from 1.11.x, use new version.
1263
    $schema = $manager->getConnection()->getSchemaManager();
1264
    $dropOldVersionTable = false;
1265
    if ($schema->tablesExist('version')) {
1266
        $columns = $schema->listTableColumns('version');
1267
        if (in_array('id', array_keys($columns), true)) {
1268
            $dropOldVersionTable = true;
1269
        }
1270
    }
1271
1272
    if ($dropOldVersionTable) {
1273
        error_log('Drop version table');
1274
        $schema->dropTable('version');
1275
    }
1276
1277
    // Creates "version" table.
1278
    $dependency->getMetadataStorage()->ensureInitialized();
1279
1280
    // Loading migrations.
1281
    $migratorConfigurationFactory = $dependency->getConsoleInputMigratorConfigurationFactory();
1282
    $result = '';
1283
    $input = new Symfony\Component\Console\Input\StringInput($result);
1284
    $migratorConfiguration = $migratorConfigurationFactory->getMigratorConfiguration($input);
1285
    $migrator = $dependency->getMigrator();
1286
    $planCalculator = $dependency->getMigrationPlanCalculator();
1287
    $migrations = $planCalculator->getMigrations();
1288
    $lastVersion = $migrations->getLast();
1289
1290
    $plan = $dependency->getMigrationPlanCalculator()->getPlanUntilVersion($lastVersion->getVersion());
1291
1292
    foreach ($plan->getItems() as $item) {
1293
        error_log("Version to be executed: ".$item->getVersion());
1294
        $item->getMigration()->setEntityManager($manager);
1295
        $item->getMigration()->setContainer(Container::$container);
1296
    }
1297
1298
    // Execute migration!!
1299
    /** @var $migratedVersions */
1300
    $versions = $migrator->migrate($plan, $migratorConfiguration);
1301
1302
    if ($debug) {
1303
        /** @var Query[] $queries */
1304
        $versionCounter = 1;
1305
        foreach ($versions as $version => $queries) {
1306
            $total = count($queries);
1307
            //echo '----------------------------------------------<br />';
1308
            $message = "VERSION: $version";
1309
            //echo "$message<br/>";
1310
            error_log('-------------------------------------');
1311
            error_log($message);
1312
            $counter = 1;
1313
            foreach ($queries as $query) {
1314
                $sql = $query->getStatement();
1315
                //echo "<code>$sql</code><br>";
1316
                error_log("$counter/$total : $sql");
1317
                $counter++;
1318
            }
1319
            $versionCounter++;
1320
        }
1321
        //echo '<br/>DONE!<br />';
1322
        error_log('DONE!');
1323
    }
1324
1325
    return true;
1326
}
1327
1328
/**
1329
 * @param string $distFile
1330
 * @param string $envFile
1331
 * @param array  $params
1332
 */
1333
function updateEnvFile($distFile, $envFile, $params)
1334
{
1335
    $requirements = [
1336
        'DATABASE_HOST',
1337
        'DATABASE_PORT',
1338
        'DATABASE_NAME',
1339
        'DATABASE_USER',
1340
        'DATABASE_PASSWORD',
1341
        'APP_INSTALLED',
1342
        'APP_ENCRYPT_METHOD',
1343
        'APP_SECRET',
1344
        'DB_MANAGER_ENABLED',
1345
        'SOFTWARE_NAME',
1346
        'SOFTWARE_URL',
1347
        'DENY_DELETE_USERS',
1348
        'HOSTING_TOTAL_SIZE_LIMIT',
1349
    ];
1350
1351
    foreach ($requirements as $requirement) {
1352
        if (!isset($params['{{'.$requirement.'}}'])) {
1353
            throw new \Exception("The parameter $requirement is needed in order to edit the .env file");
1354
        }
1355
    }
1356
1357
    $contents = file_get_contents($distFile);
1358
    $contents = str_replace(array_keys($params), array_values($params), $contents);
1359
    file_put_contents($envFile, $contents);
1360
    error_log("File env saved here: $envFile");
1361
}
1362
1363
function installTools($container, $manager, $upgrade = false)
1364
{
1365
    error_log('installTools');
1366
    // Install course tools (table "tool")
1367
    /** @var ToolChain $toolChain */
1368
    $toolChain = $container->get(ToolChain::class);
1369
    $toolChain->createTools();
1370
}
1371
1372
/**
1373
 * @param SymfonyContainer $container
1374
 * @param bool             $upgrade
1375
 */
1376
function installSchemas($container, $upgrade = false)
1377
{
1378
    error_log('installSchemas');
1379
    $settingsManager = $container->get(Chamilo\CoreBundle\Settings\SettingsManager::class);
1380
1381
    $urlRepo = $container->get(AccessUrlRepository::class);
1382
    $accessUrl = $urlRepo->find(1);
1383
    if (null === $accessUrl) {
1384
        $em = Database::getManager();
1385
1386
        // Creating AccessUrl.
1387
        $accessUrl = new AccessUrl();
1388
        $accessUrl
1389
            ->setUrl(AccessUrl::DEFAULT_ACCESS_URL)
1390
            ->setDescription('')
1391
            ->setActive(1)
1392
            ->setCreatedBy(1)
1393
        ;
1394
        $em->persist($accessUrl);
1395
        $em->flush();
1396
1397
        error_log('AccessUrl created');
1398
    }
1399
1400
    if ($upgrade) {
1401
        error_log('Upgrade settings');
1402
        $settingsManager->updateSchemas($accessUrl);
1403
    } else {
1404
        error_log('Install settings');
1405
        // Installing schemas (filling settings table)
1406
        $settingsManager->installSchemas($accessUrl);
1407
    }
1408
}
1409
1410
/**
1411
 * @param SymfonyContainer $container
1412
 */
1413
function upgradeWithContainer($container)
1414
{
1415
    Container::setContainer($container);
1416
    Container::setLegacyServices($container);
1417
    error_log('setLegacyServices');
1418
    $manager = Database::getManager();
1419
1420
    /** @var GroupRepository $repo */
1421
    $repo = $container->get(GroupRepository::class);
1422
    $repo->createDefaultGroups();
1423
1424
    // @todo check if adminId = 1
1425
    installTools($container, $manager, true);
1426
    installSchemas($container, true);
1427
}
1428
1429
/**
1430
 * After the schema was created (table creation), the function adds
1431
 * admin/platform information.
1432
 *
1433
 * @param \Psr\Container\ContainerInterface $container
1434
 * @param string                            $sysPath
1435
 * @param string                            $encryptPassForm
1436
 * @param string                            $passForm
1437
 * @param string                            $adminLastName
1438
 * @param string                            $adminFirstName
1439
 * @param string                            $loginForm
1440
 * @param string                            $emailForm
1441
 * @param string                            $adminPhoneForm
1442
 * @param string                            $languageForm
1443
 * @param string                            $institutionForm
1444
 * @param string                            $institutionUrlForm
1445
 * @param string                            $siteName
1446
 * @param string                            $allowSelfReg
1447
 * @param string                            $allowSelfRegProf
1448
 * @param string                            $installationProfile Installation profile, if any was provided
1449
 */
1450
function finishInstallationWithContainer(
1451
    $container,
1452
    $sysPath,
1453
    $encryptPassForm,
1454
    $passForm,
1455
    $adminLastName,
1456
    $adminFirstName,
1457
    $loginForm,
1458
    $emailForm,
1459
    $adminPhoneForm,
1460
    $languageForm,
1461
    $institutionForm,
1462
    $institutionUrlForm,
1463
    $siteName,
1464
    $allowSelfReg,
1465
    $allowSelfRegProf,
1466
    $installationProfile = ''
1467
) {
1468
    Container::setContainer($container);
1469
    Container::setLegacyServices($container);
1470
1471
    $timezone = api_get_timezone();
1472
1473
    $repo = Container::getUserRepository();
1474
    /** @var User $admin */
1475
    $admin = $repo->findOneBy(['username' => 'admin']);
1476
1477
    $admin
1478
        ->setLastname($adminLastName)
1479
        ->setFirstname($adminFirstName)
1480
        ->setUsername($loginForm)
1481
        ->setStatus(1)
1482
        ->setPlainPassword($passForm)
1483
        ->setEmail($emailForm)
1484
        ->setOfficialCode('ADMIN')
1485
        ->setAuthSource(PLATFORM_AUTH_SOURCE)
1486
        ->setPhone($adminPhoneForm)
1487
        ->setLocale($languageForm)
1488
        ->setTimezone($timezone)
1489
    ;
1490
1491
    $repo->updateUser($admin);
1492
1493
    $repo = Container::getUserRepository();
1494
    $repo->updateUser($admin);
1495
1496
    // Set default language
1497
    Database::update(
1498
        Database::get_main_table(TABLE_MAIN_LANGUAGE),
1499
        ['available' => 1],
1500
        ['english_name = ?' => $languageForm]
1501
    );
1502
1503
    // Install settings
1504
    installSettings(
1505
        $institutionForm,
1506
        $institutionUrlForm,
1507
        $siteName,
1508
        $emailForm,
1509
        $adminLastName,
1510
        $adminFirstName,
1511
        $languageForm,
1512
        $allowSelfReg,
1513
        $allowSelfRegProf,
1514
        $installationProfile
1515
    );
1516
    lockSettings();
1517
    updateDirAndFilesPermissions();
1518
}
1519
1520
/**
1521
 * Update settings based on installation profile defined in a JSON file.
1522
 *
1523
 * @param string $installationProfile The name of the JSON file in main/install/profiles/ folder
1524
 *
1525
 * @return bool false on failure (no bad consequences anyway, just ignoring profile)
1526
 */
1527
function installProfileSettings($installationProfile = '')
1528
{
1529
    error_log('installProfileSettings');
1530
    if (empty($installationProfile)) {
1531
        return false;
1532
    }
1533
    $jsonPath = api_get_path(SYS_PATH).'main/install/profiles/'.$installationProfile.'.json';
1534
    // Make sure the path to the profile is not hacked
1535
    if (!Security::check_abs_path($jsonPath, api_get_path(SYS_PATH).'main/install/profiles/')) {
1536
        return false;
1537
    }
1538
    if (!is_file($jsonPath)) {
1539
        return false;
1540
    }
1541
    if (!is_readable($jsonPath)) {
1542
        return false;
1543
    }
1544
    if (!function_exists('json_decode')) {
1545
        // The php-json extension is not available. Ignore profile.
1546
        return false;
1547
    }
1548
    $json = file_get_contents($jsonPath);
1549
    $params = json_decode($json);
1550
    if (false === $params or null === $params) {
1551
        return false;
1552
    }
1553
    $settings = $params->params;
1554
    if (!empty($params->parent)) {
1555
        installProfileSettings($params->parent);
1556
    }
1557
1558
    $tblSettings = Database::get_main_table(TABLE_MAIN_SETTINGS);
1559
1560
    foreach ($settings as $id => $param) {
1561
        $conditions = ['variable = ? ' => $param->variable];
1562
1563
        if (!empty($param->subkey)) {
1564
            $conditions['AND subkey = ? '] = $param->subkey;
1565
        }
1566
1567
        Database::update(
1568
            $tblSettings,
1569
            ['selected_value' => $param->selected_value],
1570
            $conditions
1571
        );
1572
    }
1573
1574
    return true;
1575
}
1576
1577
/**
1578
 * Quick function to remove a directory with its subdirectories.
1579
 *
1580
 * @param $dir
1581
 */
1582
function rrmdir($dir)
1583
{
1584
    if (is_dir($dir)) {
1585
        $objects = scandir($dir);
1586
        foreach ($objects as $object) {
1587
            if ('.' != $object && '..' != $object) {
1588
                if ('dir' == filetype($dir.'/'.$object)) {
1589
                    @rrmdir($dir.'/'.$object);
1590
                } else {
1591
                    @unlink($dir.'/'.$object);
1592
                }
1593
            }
1594
        }
1595
        reset($objects);
1596
        rmdir($dir);
1597
    }
1598
}
1599
1600
/**
1601
 * Control the different steps of the migration through a big switch.
1602
 *
1603
 * @param string        $fromVersion
1604
 * @param EntityManager $manager
1605
 * @param bool          $processFiles
1606
 *
1607
 * @return bool Always returns true except if the process is broken
1608
 */
1609
function migrateSwitch($fromVersion, $manager, $processFiles = true)
1610
{
1611
    error_log('-----------------------------------------');
1612
    error_log('Starting migration process from '.$fromVersion.' ('.date('Y-m-d H:i:s').')');
1613
    //echo '<a class="btn btn--secondary" href="javascript:void(0)" id="details_button">'.get_lang('Details').'</a><br />';
1614
    //echo '<div id="details" style="display:none">';
1615
    $connection = $manager->getConnection();
1616
1617
    switch ($fromVersion) {
1618
        case '1.11.0':
1619
        case '1.11.1':
1620
        case '1.11.2':
1621
        case '1.11.4':
1622
        case '1.11.6':
1623
        case '1.11.8':
1624
        case '1.11.10':
1625
        case '1.11.12':
1626
        case '1.11.14':
1627
        case '1.11.16':
1628
            $start = time();
1629
            // Migrate using the migration files located in:
1630
            // /srv/http/chamilo2/src/CoreBundle/Migrations/Schema/V200
1631
            $result = migrate($manager);
1632
            error_log('-----------------------------------------');
1633
1634
            if ($result) {
1635
                error_log('Migrations files were executed ('.date('Y-m-d H:i:s').')');
1636
                $sql = "UPDATE settings SET selected_value = '2.0.0'
1637
                        WHERE variable = 'chamilo_database_version'";
1638
                $connection->executeQuery($sql);
1639
                if ($processFiles) {
1640
                    error_log('Update config files');
1641
                    include __DIR__.'/update-files-1.11.0-2.0.0.inc.php';
1642
                    // Only updates the configuration.inc.php with the new version
1643
                    //include __DIR__.'/update-configuration.inc.php';
1644
                }
1645
                $finish = time();
1646
                $total = round(($finish - $start) / 60);
1647
                error_log('Database migration finished:  ('.date('Y-m-d H:i:s').') took '.$total.' minutes');
1648
            } else {
1649
                error_log('There was an error during running migrations. Check error.log');
1650
                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...
1651
            }
1652
            break;
1653
        default:
1654
            break;
1655
    }
1656
1657
    //echo '</div>';
1658
1659
    return true;
1660
}
1661
1662
/**
1663
 * @return string
1664
 */
1665
function generateRandomToken()
1666
{
1667
    return hash('sha1', uniqid(mt_rand(), true));
1668
}
1669
1670
/**
1671
 * This function checks if the given file can be created or overwritten.
1672
 *
1673
 * @param string $file Full path to a file
1674
 */
1675
function checkCanCreateFile(string $file): bool
1676
{
1677
    if (file_exists($file)) {
1678
        return is_writable($file);
1679
    }
1680
1681
    $write = @file_put_contents($file, '');
1682
1683
    if (false !== $write) {
1684
        unlink($file);
1685
1686
        return true;
1687
    }
1688
1689
    return false;
1690
}
1691
1692
/**
1693
 * Checks if the update option is available.
1694
 *
1695
 * This function checks the APP_INSTALLED environment variable to determine if the application is already installed.
1696
 * If the APP_INSTALLED variable is set to '1', it indicates that an update is available.
1697
 *
1698
 * @return bool True if the application is already installed (APP_INSTALLED='1'), otherwise false.
1699
 */
1700
function isUpdateAvailable(): bool
1701
{
1702
    // Check if APP_INSTALLED is set and equals '1'
1703
    if (isset($_ENV['APP_INSTALLED']) && $_ENV['APP_INSTALLED'] === '1') {
1704
        return true;
1705
    }
1706
1707
    // If APP_INSTALLED is not found or not set to '1', assume the application is not installed
1708
    return false;
1709
}
1710
1711
function checkMigrationStatus(): array
1712
{
1713
    $resultStatus = [
1714
        'status' => false,
1715
        'message' => 'Error executing migrations status command.',
1716
        'current_migration' => null,
1717
        'progress_percentage' => 0
1718
    ];
1719
1720
    $dotenv = new Dotenv();
1721
    $envFile = api_get_path(SYMFONY_SYS_PATH) . '.env';
1722
    $dotenv->loadEnv($envFile);
1723
1724
    try {
1725
        connectToDatabase(
1726
            $_ENV['DATABASE_HOST'],
1727
            $_ENV['DATABASE_USER'],
1728
            $_ENV['DATABASE_PASSWORD'],
1729
            $_ENV['DATABASE_NAME'],
1730
            $_ENV['DATABASE_PORT']
1731
        );
1732
1733
        $manager = Database::getManager();
1734
1735
        $connection = $manager->getConnection();
1736
1737
        // Loading migration configuration.
1738
        $config = new PhpFile('./migrations.php');
1739
        $dependency = DependencyFactory::fromConnection($config, new ExistingConnection($connection));
1740
1741
        // Check if old "version" table exists from 1.11.x, use new version.
1742
        $schema = $manager->getConnection()->createSchemaManager();
1743
        $hasOldVersionTable = false;
1744
        $anyVersionYet = !$schema->tablesExist('version');
1745
        $isVersionEmpty = false;
1746
        $executedMigrations = 0;
1747
        $currentMigration = '';
1748
        if ($schema->tablesExist('version')) {
1749
            $columns = $schema->listTableColumns('version');
1750
            if (in_array('id', array_keys($columns), true)) {
1751
                $hasOldVersionTable = true;
1752
            }
1753
            $query = $connection->createQueryBuilder()
1754
                ->select('*')
1755
                ->from('version');
1756
            $result = $query->execute();
1757
            $executedMigrations = $result->rowCount();
1758
            $isVersionEmpty = ($executedMigrations == 0);
1759
            if (!$isVersionEmpty) {
1760
                $query = $connection->createQueryBuilder()
1761
                    ->select('*')
1762
                    ->from('version')
1763
                    ->orderBy('executed_at', 'DESC')
1764
                    ->setMaxResults(1);
1765
                $result = $query->execute()->fetch();
1766
                $currentMigration = $result['version'];
1767
            }
1768
        }
1769
1770
        if (!$hasOldVersionTable) {
1771
            // Loading migrations.
1772
            $migratorConfigurationFactory = $dependency->getConsoleInputMigratorConfigurationFactory();
1773
            $result = '';
1774
            $input = new Symfony\Component\Console\Input\StringInput($result);
1775
            $planCalculator = $dependency->getMigrationPlanCalculator();
1776
            $migrations = $planCalculator->getMigrations();
1777
            $totalMigrations = $migrations->count();
1778
            $lastVersion = $migrations->getLast();
1779
1780
            if ($anyVersionYet || $isVersionEmpty) {
1781
                $currentMigration = '';
1782
                $executedMigrations = 0;
1783
            }
1784
1785
            // Calculate progress percentage
1786
            $progressPercentage = ceil(($executedMigrations / $totalMigrations) * 100);
1787
            $resultStatus = [
1788
                'status' => ($progressPercentage >= 100),
1789
                'message' => ($progressPercentage >= 100) ? 'All migrations have been executed.' : 'Migrations are pending.',
1790
                'current_migration' => ($progressPercentage >= 100) ? null : $currentMigration,
1791
                'progress_percentage' => $progressPercentage
1792
            ];
1793
        }
1794
    } catch (Exception) {
1795
        return $resultStatus;
1796
    }
1797
1798
    return $resultStatus;
1799
}
1800