executeMigration()   A
last analyzed

Complexity

Conditions 4
Paths 47

Size

Total Lines 59
Code Lines 39

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 39
nc 47
nop 0
dl 0
loc 59
rs 9.296
c 0
b 0
f 0

How to fix   Long Method   

Long Method

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

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

Commonly applied refactorings include:

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