Skip to content

Commit 777bb31

Browse files
committed
Merge branch 'master' of github.com:chamilo/chamilo-lms
2 parents 5754cc4 + b3633f0 commit 777bb31

File tree

87 files changed

+752
-393
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

87 files changed

+752
-393
lines changed

public/documentation/installation_guide.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -516,7 +516,7 @@ <h2>7. Xapian text indexation</h2>
516516
This is probably not supported for Windows setups (untested), but it should work on Linux and macOS.<br>
517517
To install Xapian (a requirement to use the internal search engine), follow the instructions below.<br>
518518
<pre>
519-
sudo apt install libxapian-dev catdoc html2text unrtf ghostscript poppler-utils build-essential
519+
sudo apt install libxapian-dev catdoc html2text unrtf ghostscript poppler-utils build-essential php-dev
520520
cd /tmp
521521
wget https://oligarchy.co.uk/xapian/1.4.25/xapian-bindings-1.4.25.tar.xz
522522
# use 1.4.25 because this matches the version of the libxapian-dev package on Ubuntu 25.04

public/main/cron/user_import/resend_email_with_new_password.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,10 +69,10 @@
6969
null,
7070
PERSON_NAME_EMAIL_ADDRESS
7171
);
72-
$emailsubject = '['.api_get_setting('siteName').'] '.get_lang('Your registration on').' '.api_get_setting('siteName');
73-
$emailbody = sprintf(get_lang('Dear %s,'), api_get_person_name($user['FirstName'], $user['LastName']))."\n\n".get_lang('You are registered to')." ".api_get_setting('siteName')." ".get_lang('with the following settings:')."\n\n".get_lang('Username')." : ".$user['UserName']."\n".get_lang('Password')." : ".$user['Password']."\n\n".get_lang('Address')." ".api_get_setting('siteName')." ".get_lang('is')." : ".api_get_path(WEB_PATH)." \n\n".get_lang('In case of trouble, contact us.')."\n\n".get_lang('Formula').",\n\n".api_get_person_name(api_get_setting('administratorName'), api_get_setting('administratorSurname'))."\n".get_lang('Administrator')." ".api_get_setting('siteName')."\nT. ".api_get_setting('administratorTelephone')."\n".get_lang('E-mail')." : ".api_get_setting('emailAdministrator');
74-
$sender_name = api_get_person_name(api_get_setting('administratorName'), api_get_setting('administratorSurname'), null, PERSON_NAME_EMAIL_ADDRESS);
75-
$email_admin = api_get_setting('emailAdministrator');
72+
$emailsubject = '['.api_get_setting('site_name').'] '.sprintf(get_lang('Your registration on %s'), api_get_setting('site_name'));
73+
$emailbody = sprintf(get_lang('Dear %s,'), api_get_person_name($user['FirstName'], $user['LastName']))."\n\n".get_lang('You are registered to')." ".api_get_setting('site_name')." ".get_lang('with the following settings:')."\n\n".get_lang('Username')." : ".$user['UserName']."\n".get_lang('Password')." : ".$user['Password']."\n\n".get_lang('Address')." ".api_get_setting('site_name')." ".get_lang('is')." : ".api_get_path(WEB_PATH)." \n\n".get_lang('In case of trouble, contact us.')."\n\n".get_lang('Formula').",\n\n".api_get_person_name(api_get_setting('administrator_name'), api_get_setting('administrator_surname'))."\n".get_lang('Administrator')." ".api_get_setting('site_name')."\nT. ".api_get_setting('administrator_phone')."\n".get_lang('E-mail')." : ".api_get_setting('administrator_email');
74+
$sender_name = api_get_person_name(api_get_setting('administrator_name'), api_get_setting('administrator_surname'), null, PERSON_NAME_EMAIL_ADDRESS);
75+
$email_admin = api_get_setting('administrator_email');
7676
@api_mail_html(
7777
$recipient_name,
7878
$user['Email'],

public/main/inc/ajax/user_manager.ajax.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@
206206
PERSON_NAME_EMAIL_ADDRESS
207207
);
208208

209-
$subject = '['.api_get_setting('siteName').'] '.get_lang('Your registration on').' '.api_get_setting('siteName');
209+
$subject = '['.api_get_setting('siteName').'] '.sprintf(get_lang('Your registration on %s'), api_get_setting('site_name'));
210210
$emailAdmin = api_get_setting('emailAdministrator');
211211
$sender_name = api_get_person_name(
212212
api_get_setting('administratorName'),

public/main/inc/lib/document.lib.php

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3263,6 +3263,9 @@ public static function addDocument(
32633263

32643264
// Document already exists
32653265
if (null !== $document) {
3266+
// Keep the contextual tree consistent: set ResourceLink.parent when needed.
3267+
self::syncResourceLinkParentForContext($document, $parentResource, $courseEntity, $session, $group);
3268+
32663269
return $document;
32673270
}
32683271

@@ -3281,6 +3284,9 @@ public static function addDocument(
32813284
$em->persist($document);
32823285
$em->flush();
32833286

3287+
// Ensure contextual hierarchy (course/session/group) uses ResourceLink.parent.
3288+
self::syncResourceLinkParentForContext($document, $parentResource, $courseEntity, $session, $group);
3289+
32843290
$repo = Container::getDocumentRepository();
32853291
if (!empty($content)) {
32863292
$repo->addFileFromString($document, $title, 'text/html', $content, true);
@@ -3318,4 +3324,108 @@ public static function addDocument(
33183324

33193325
return false;
33203326
}
3327+
3328+
private static function syncResourceLinkParentForContext(
3329+
CDocument $child,
3330+
$parentResource,
3331+
$courseEntity,
3332+
$session,
3333+
$group
3334+
): void {
3335+
// Only set a parent link when the parent is another document (folder).
3336+
if (!$parentResource instanceof CDocument) {
3337+
return; // Root items must keep rl.parent = NULL
3338+
}
3339+
3340+
if (!method_exists($child, 'getResourceNode') || !method_exists($parentResource, 'getResourceNode')) {
3341+
return;
3342+
}
3343+
3344+
$childNode = $child->getResourceNode();
3345+
$parentNode = $parentResource->getResourceNode();
3346+
3347+
if (!$childNode instanceof ResourceNode || !$parentNode instanceof ResourceNode) {
3348+
return;
3349+
}
3350+
3351+
try {
3352+
$em = Database::getManager();
3353+
3354+
$childLink = self::findResourceLinkForContext($em, $childNode, $courseEntity, $session, $group);
3355+
$parentLink = self::findResourceLinkForContext($em, $parentNode, $courseEntity, $session, $group);
3356+
3357+
if (!$childLink instanceof ResourceLink || !$parentLink instanceof ResourceLink) {
3358+
// If a link is missing, we don't hard-fail the import.
3359+
error_log('[IMPORT] ResourceLink not found for context when syncing parent.', [
3360+
'childNodeId' => method_exists($childNode, 'getId') ? $childNode->getId() : null,
3361+
'parentNodeId' => method_exists($parentNode, 'getId') ? $parentNode->getId() : null,
3362+
]);
3363+
3364+
return;
3365+
}
3366+
3367+
$currentParent = method_exists($childLink, 'getParent') ? $childLink->getParent() : null;
3368+
3369+
// Avoid useless flushes
3370+
if ($currentParent && method_exists($currentParent, 'getId') && method_exists($parentLink, 'getId')) {
3371+
if ((int) $currentParent->getId() === (int) $parentLink->getId()) {
3372+
return;
3373+
}
3374+
}
3375+
3376+
$childLink->setParent($parentLink);
3377+
$em->persist($childLink);
3378+
$em->flush();
3379+
} catch (\Throwable $e) {
3380+
error_log('[IMPORT] Failed to sync ResourceLink.parent for context: '.$e->getMessage());
3381+
}
3382+
}
3383+
3384+
private static function findResourceLinkForContext(
3385+
$em,
3386+
ResourceNode $node,
3387+
$courseEntity,
3388+
$session,
3389+
$group
3390+
): ?ResourceLink {
3391+
try {
3392+
$qb = $em->createQueryBuilder()
3393+
->select('rl')
3394+
->from(ResourceLink::class, 'rl')
3395+
->andWhere('rl.resourceNode = :node')
3396+
->setParameter('node', $node);
3397+
3398+
// Course context
3399+
if ($courseEntity) {
3400+
$qb->andWhere('rl.course = :course')->setParameter('course', $courseEntity);
3401+
} else {
3402+
$qb->andWhere('rl.course IS NULL');
3403+
}
3404+
3405+
// Session context
3406+
if ($session) {
3407+
$qb->andWhere('rl.session = :session')->setParameter('session', $session);
3408+
} else {
3409+
$qb->andWhere('rl.session IS NULL');
3410+
}
3411+
3412+
// Group context
3413+
if ($group) {
3414+
$qb->andWhere('rl.group = :group')->setParameter('group', $group);
3415+
} else {
3416+
$qb->andWhere('rl.group IS NULL');
3417+
}
3418+
3419+
$qb->setMaxResults(1);
3420+
3421+
/** @var ResourceLink|null $link */
3422+
$link = $qb->getQuery()->getOneOrNullResult();
3423+
3424+
return $link;
3425+
} catch (\Throwable $e) {
3426+
error_log('[IMPORT] Failed to find ResourceLink for context: '.$e->getMessage());
3427+
3428+
return null;
3429+
}
3430+
}
33213431
}

public/main/inc/lib/myspace.lib.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3015,17 +3015,17 @@ function (array &$user) {
30153015
$addedto = '';
30163016
if ($sendMail) {
30173017
foreach ($users as $index => $user) {
3018-
$emailsubject = '['.api_get_setting('siteName').'] '.get_lang('Your registration on').' '.api_get_setting('siteName');
3018+
$emailsubject = '['.api_get_setting('site_name').'] '.sprintf(get_lang('Your registration on %s'), api_get_setting('site_name'));
30193019
$emailbody = get_lang('Dear').' '.
30203020
api_get_person_name($user['First name'], $user['Last name']).",\n\n".
3021-
get_lang('You are registered to')." ".api_get_setting('siteName')." ".get_lang('with the following settings:')."\n\n".
3021+
get_lang('You are registered to')." ".api_get_setting('site_name')." ".get_lang('with the following settings:')."\n\n".
30223022
get_lang('Username')." : $user[UserName]\n".
30233023
get_lang('Pass')." : $user[Password]\n\n".
3024-
get_lang('The address of')." ".api_get_setting('siteName')." ".get_lang('is')." : ".api_get_path(WEB_PATH)." \n\n".
3024+
get_lang('The address of')." ".api_get_setting('site_name')." ".get_lang('is')." : ".api_get_path(WEB_PATH)." \n\n".
30253025
get_lang('In case of trouble, contact us.')."\n\n".
30263026
get_lang('Sincerely').",\n\n".
30273027
api_get_person_name(api_get_setting('administratorName'), api_get_setting('administratorSurname'))."\n".
3028-
get_lang('Administrator')." ".api_get_setting('siteName')."\nT. ".
3028+
get_lang('Administrator')." ".api_get_setting('site_name')."\nT. ".
30293029
api_get_setting('administratorTelephone')."\n".get_lang('E-mail')." : ".api_get_setting('emailAdministrator');
30303030

30313031
api_mail_html(

public/main/inc/lib/usermanager.lib.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1175,7 +1175,7 @@ public static function update_user(
11751175

11761176
if (!empty($email) && $send_email) {
11771177
$recipient_name = api_get_person_name($firstname, $lastname, null, PERSON_NAME_EMAIL_ADDRESS);
1178-
$emailsubject = '['.api_get_setting('siteName').'] '.get_lang('Your registration on').' '.api_get_setting('siteName');
1178+
$emailsubject = '['.api_get_setting('site_name').'] '.sprintf(get_lang('Your registration on %s'), api_get_setting('site_name'));
11791179
$sender_name = api_get_person_name(
11801180
api_get_setting('administratorName'),
11811181
api_get_setting('administratorSurname'),

public/main/install/index.php

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,58 @@
7070
Container::$session = new HttpSession();
7171

7272
require_once 'install.lib.php';
73+
74+
$envFile = api_get_path(SYMFONY_SYS_PATH).'.env';
75+
$versionInfo = require __DIR__.'/version.php';
76+
$installerVersion = $versionInfo['new_version'] ?? null;
77+
78+
if (file_exists($envFile)) {
79+
$dotenv = new Dotenv();
80+
try {
81+
// Load .env without crashing if incomplete
82+
$dotenv->loadEnv($envFile);
83+
} catch (\Throwable $e) {
84+
// Ignore and let the wizard continue
85+
}
86+
87+
$appInstalled = (($_ENV['APP_INSTALLED'] ?? getenv('APP_INSTALLED') ?? '') === '1');
88+
89+
if ($appInstalled) {
90+
// Try to read DB version; fail soft if DB is unreachable
91+
$dbVersion = null;
92+
try {
93+
$dbHost = $_ENV['DATABASE_HOST'] ?? 'localhost';
94+
$dbUser = $_ENV['DATABASE_USER'] ?? '';
95+
$dbPass = $_ENV['DATABASE_PASSWORD'] ?? '';
96+
$dbName = $_ENV['DATABASE_NAME'] ?? '';
97+
$dbPort = (int) ($_ENV['DATABASE_PORT'] ?? 3306);
98+
99+
connectToDatabase($dbHost, $dbUser, $dbPass, $dbName, $dbPort);
100+
$dbVersion = get_config_param_from_db('chamilo_database_version');
101+
} catch (\Throwable $e) {
102+
// Leave $dbVersion as null
103+
}
104+
105+
// If DB version is >= installer version, block the wizard
106+
if ($installerVersion && $dbVersion && version_compare($dbVersion, $installerVersion, '>=')) {
107+
header('HTTP/1.1 409 Conflict');
108+
echo '<!doctype html><meta charset="utf-8"><title>Chamilo already installed</title>';
109+
echo '<div style="font-family:system-ui;max-width:760px;margin:64px auto;padding:24px;border:1px solid #e5e7eb;border-radius:12px">';
110+
echo '<h1>Chamilo is already installed</h1>';
111+
echo '<p>Database version: <strong>'.htmlspecialchars($dbVersion).'</strong>. '
112+
.'Installer version: <strong>'.htmlspecialchars($installerVersion).'</strong>.</p>';
113+
echo '<p>The install wizard is disabled because the platform is already installed and up-to-date.</p>';
114+
echo '<p>If you need a fresh install, set <code>APP_INSTALLED=0</code> or remove <code>.env</code> first.</p>';
115+
echo '</div>';
116+
exit;
117+
}
118+
119+
// If DB version < installer version, let the wizard run (upgrade path)
120+
// If we could not read DB version (null), also allow the wizard (conservative).
121+
}
122+
// If APP_INSTALLED != 1, allow a fresh install
123+
}
124+
73125
$httpRequest = Request::createFromGlobals();
74126
$installationLanguage = 'en_US';
75127

public/main/install/install.lib.php

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -467,7 +467,7 @@ function display_requirements(
467467
$timezone = checkPhpSettingExists('date.timezone');
468468

469469
$phpVersion = phpversion();
470-
$isVersionPassed = version_compare($phpVersion, REQUIRED_PHP_VERSION, '<=') <= 1;
470+
$isVersionPassed = version_compare($phpVersion, REQUIRED_PHP_VERSION, '>=');
471471

472472
$extensions = [];
473473
$extensions[] = [
@@ -1800,17 +1800,55 @@ function checkCanCreateFile(string $file): bool
18001800
*/
18011801
function isUpdateAvailable(): bool
18021802
{
1803-
$dotenv = new Dotenv();
18041803
$envFile = api_get_path(SYMFONY_SYS_PATH) . '.env';
1805-
$dotenv->loadEnv($envFile);
1804+
if (!file_exists($envFile)) {
1805+
return false; // No .env -> fresh install
1806+
}
1807+
1808+
$dotenv = new Dotenv();
1809+
try {
1810+
$dotenv->loadEnv($envFile);
1811+
} catch (\Throwable $e) {
1812+
// If .env cannot be parsed, play safe: no update banner
1813+
return false;
1814+
}
1815+
1816+
// Must be an installed platform
1817+
if (($_ENV['APP_INSTALLED'] ?? '') !== '1') {
1818+
return false;
1819+
}
18061820

1807-
// Check if APP_INSTALLED is set and equals '1'
1808-
if (isset($_ENV['APP_INSTALLED']) && $_ENV['APP_INSTALLED'] === '1') {
1821+
// Compare DB version vs installer version
1822+
$versionInfo = require __DIR__.'/version.php';
1823+
$installerVersion = $versionInfo['new_version'] ?? null;
1824+
1825+
if (!$installerVersion) {
1826+
// If we cannot know installer version, do not show update banner
1827+
return false;
1828+
}
1829+
1830+
$dbVersion = null;
1831+
try {
1832+
connectToDatabase(
1833+
$_ENV['DATABASE_HOST'] ?? 'localhost',
1834+
$_ENV['DATABASE_USER'] ?? '',
1835+
$_ENV['DATABASE_PASSWORD'] ?? '',
1836+
$_ENV['DATABASE_NAME'] ?? '',
1837+
(int) ($_ENV['DATABASE_PORT'] ?? 3306)
1838+
);
1839+
$dbVersion = get_config_param_from_db('chamilo_database_version');
1840+
} catch (\Throwable $e) {
1841+
// If DB is unreachable but platform is marked installed, allow upgrade path
1842+
// so the UI can guide the admin.
18091843
return true;
18101844
}
18111845

1812-
// If APP_INSTALLED is not found or not set to '1', assume the application is not installed
1813-
return false;
1846+
if (empty($dbVersion)) {
1847+
// No version recorded -> offer upgrade path to normalize state
1848+
return true;
1849+
}
1850+
1851+
return version_compare($dbVersion, $installerVersion, '<');
18141852
}
18151853

18161854
/**

public/main/my_space/user_edit.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@
155155
);
156156

157157
if (!empty($email) && $send_mail) {
158-
$emailsubject = '['.api_get_setting('siteName').'] '.get_lang('Your registration on').' '.api_get_setting('siteName');
158+
$emailsubject = '['.api_get_setting('site_name').'] '.sprintf(get_lang('Your registration on %s'), api_get_setting('site_name'));
159159
$portal_url = api_get_path(WEB_PATH);
160160
if (api_is_multiple_url_enabled()) {
161161
$access_url_id = api_get_current_access_url_id();
@@ -167,11 +167,11 @@
167167

168168
$emailbody = get_lang('Dear')." ".
169169
stripslashes(api_get_person_name($userInfo['firstname'], $userInfo['lastname'])).",\n\n".
170-
get_lang('You are registered to')." ".api_get_setting('siteName')." ".
170+
get_lang('You are registered to')." ".api_get_setting('site_name')." ".
171171
get_lang('with the following settings:')."\n\n".
172172
get_lang('Username')." : ".$username."\n".
173173
get_lang('Pass')." : ".stripslashes($password)."\n\n".
174-
get_lang('The address of')." ".api_get_setting('siteName')." ".
174+
get_lang('The address of')." ".api_get_setting('site_name')." ".
175175
get_lang('is')." : ".$portal_url."\n\n".
176176
get_lang('In case of trouble, contact us.')."\n\n".
177177
get_lang('Sincerely').",\n\n".
@@ -180,7 +180,7 @@
180180
api_get_setting('administratorSurname')
181181
)."\n".
182182
get_lang('Administrator')." ".
183-
api_get_setting('siteName')."\nT. ".
183+
api_get_setting('site_name')."\nT. ".
184184
api_get_setting('administratorTelephone')."\n".
185185
get_lang('E-mail')." : ".api_get_setting('emailAdministrator');
186186

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{{ '[' ~ _s.site_name ~ '] ' ~ 'Your registration on'|get_lang ~ ' ' ~ _s.site_name }}
1+
{{ '[' ~ _s.site_name ~ '] ' ~ 'Your registration on %s'|trans({'%s', _s.site_name})}}

0 commit comments

Comments
 (0)