Passed
Push — dependabot/npm_and_yarn/microm... ( e84ba6...f2f212 )
by
unknown
10:03
created

checkMigrationStatus()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 31
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

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