Skip to content

Commit 14454ba

Browse files
committed
Merge branch 'master' of github.com:chamilo/chamilo-lms
2 parents 97b8d38 + 37a8f15 commit 14454ba

File tree

6 files changed

+269
-79
lines changed

6 files changed

+269
-79
lines changed

public/documentation/installation_guide.html

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ <h2>1. Requirements</h2>
4040
<li>xml</li>
4141
<li>zip</li>
4242
<li>zlib</li>
43-
<li>ldap (optional, but mandatory in dev env)</li>
43+
<li>ldap</li>
4444
<li>redis (optional)</li>
4545
<li>xapian (optional)</li>
4646
</ul>
@@ -96,7 +96,15 @@ <h3>2.5. Installation wizard</h3>
9696
<h3>2.6. Sessions and Redis</h3>
9797
<a id="web-install-sessions" class="anchor"></a>
9898
In this beta version, there are known issues with sessions not getting updated fast enough, which can be solved (temporarily) by using a Redis server. Check the command line instructions for more about this. This is a temporary situation that we expect to fix before the stable release.
99-
99+
100+
<h3>2.7. Clean up</h3>h3>
101+
<a id="web-cleanup" class="anchor"></a>
102+
As recommended by the installer's last page, make sure you change permissions on the config/ dir and the .env file, and you delete the public/main/install/ folder.
103+
<pre>
104+
sudo chown -R root: config/ .env
105+
sudo rm -r public/main/install/
106+
</pre>
107+
100108
<h2>3. Command line installation</h2>
101109
<a id="cli-install" class="anchor"></a>
102110

@@ -106,7 +114,7 @@ <h3>3.1. Software stack</h3>
106114
<pre>
107115
sudo -s
108116
apt update && apt -y upgrade
109-
apt -y install apache2 libapache2-mod-php8.3 mariadb-client mariadb-server redis-server php8.3-{bcmath,curl,exif,gd,iconv,intl,mbstring,mysql,opcache,soap,xml,zip,redis}
117+
apt -y install apache2 libapache2-mod-php8.3 mariadb-client mariadb-server redis-server php8.3-{bcmath,curl,exif,gd,iconv,intl,ldap,mbstring,mysql,opcache,soap,xml,zip,redis}
110118
a2enmod rewrite
111119
cd /var/www/
112120
mkdir chamilo
@@ -116,7 +124,7 @@ <h3>3.1. Software stack</h3>
116124
rm chamilo2.0.0-beta.1.tar.gz
117125
cd chamilo
118126
touch .env
119-
chown -R www-data: var .env
127+
chown -R www-data: var/ config/ .env
120128
mysql -u root -e "GRANT ALL PRIVILEGES ON chamilo.* TO chamilo@localhost IDENTIFIED BY '[choose a password here]'";
121129
mysql -u root -e "FLUSH PRIVILEGES;"
122130
mysql_secure_installation
@@ -179,7 +187,7 @@ <h3>3.3. Web server</h3>
179187
systemctl restart apache2</pre>
180188
<h3>3.4. Files permissions</h3>
181189
<a id="cli-install-permissions" class="anchor"></a>
182-
Make sure the following files and folders are writeable by the web server. Set permissions to 0770 for example:
190+
Make sure the following files and folders are writeable by the web server during the installation. Set permissions to 0770 or the owner to www-data, for example:
183191
<ul>
184192
<li>var/</li>
185193
<li>config/</li>
@@ -202,6 +210,14 @@ <h3>3.6. Installation wizard</h3>
202210
You should now be able to direct your browser to your URL (e.g. <em>[http://my.chamilo.local]</em>).<br>
203211
Chamilo will pick it up from there and offer the installation wizard to help guide you through the rest of the process.
204212

213+
<h3>3.7. Clean up</h3>h3>
214+
<a id="cli-cleanup" class="anchor"></a>
215+
As recommended by the installer's last page, make sure you change permissions on the config/ dir and the .env file, and you delete the public/main/install/ folder.
216+
<pre>
217+
sudo chown -R root: config/ .env
218+
sudo rm -r public/main/install/
219+
</pre>
220+
205221
<h2>4. Command line upgrade from 1.11.*</h2>
206222
<a id="cli-upgrade" class="anchor"></a>
207223
<h3>4.1. Database</h3>
@@ -263,7 +279,7 @@ <h4>6.2.1. WampServer</h4>
263279
<a href="https://www.wampserver.com/">Download</a> and install WampServer (accept default options if in doubt).<br>
264280
Restart WampServer to make these changes effective.<br>
265281
Open http://localhost/ in your browser. If the WampServer welcome screen with server configuration info appears, it means WampServer is working.<br>
266-
Click on the WampServer icon in your status bar and go to PHP -&gt; Extensions. Make sure all the following extensions are enabled: php_curl, php_gd, php_intl, php_mbstring, php_mysqli, php_soap, php_xml, php_zip<br>
282+
Click on the WampServer icon in your status bar and go to PHP -&gt; Extensions. Make sure all the following extensions are enabled: php_curl, php_gd, php_intl, php_ldap, php_mbstring, php_mysqli, php_soap, php_xml, php_zip<br>
267283
Edit C:\wamp64\bin\php\php8.3\php.ini, locate ";extension=sodium" and remove the ";" from the beginning of the line.<br>
268284
Also, update the "memory_limit" setting from "128M" to "2048M" and a few other settings as shown below.<br>
269285
<pre>

src/CoreBundle/Controller/Api/DownloadSelectedDocumentsAction.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,10 @@ function () use ($documents, $zipName): void {
7777
$this->addNodeToZip($zip, $node);
7878
}
7979

80+
if (0 === count($zip->files)) {
81+
$zip->addFile('.empty', '');
82+
}
83+
8084
$zip->finish();
8185
},
8286
Response::HTTP_CREATED

src/CoreBundle/Helpers/PageHelper.php

Lines changed: 85 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,34 @@
1212
use Chamilo\CoreBundle\Entity\User;
1313
use Chamilo\CoreBundle\Repository\PageCategoryRepository;
1414
use Chamilo\CoreBundle\Repository\PageRepository;
15+
use Chamilo\CoreBundle\Repository\SysAnnouncementRepository;
16+
use Symfony\Component\Security\Core\User\UserInterface;
1517

1618
class PageHelper
1719
{
1820
protected PageRepository $pageRepository;
1921
protected PageCategoryRepository $pageCategoryRepository;
2022

21-
public function __construct(PageRepository $pageRepository, PageCategoryRepository $pageCategoryRepository)
22-
{
23+
/**
24+
* Repository used to read system announcements (platform news).
25+
*/
26+
protected SysAnnouncementRepository $sysAnnouncementRepository;
27+
28+
/**
29+
* Helper used to retrieve the current AccessUrl.
30+
*/
31+
protected AccessUrlHelper $accessUrlHelper;
32+
33+
public function __construct(
34+
PageRepository $pageRepository,
35+
PageCategoryRepository $pageCategoryRepository,
36+
SysAnnouncementRepository $sysAnnouncementRepository,
37+
AccessUrlHelper $accessUrlHelper
38+
) {
2339
$this->pageRepository = $pageRepository;
2440
$this->pageCategoryRepository = $pageCategoryRepository;
41+
$this->sysAnnouncementRepository = $sysAnnouncementRepository;
42+
$this->accessUrlHelper = $accessUrlHelper;
2543
}
2644

2745
public function createDefaultPages(User $user, AccessUrl $url, string $locale): bool
@@ -99,8 +117,7 @@ public function createDefaultPages(User $user, AccessUrl $url, string $locale):
99117

100118
$this->pageCategoryRepository->update($footerPrivateCategory);
101119

102-
// Categories for extra content in admin blocks
103-
120+
// Categories for extra content in admin blocks.
104121
foreach (self::getCategoriesForAdminBlocks() as $nameBlock) {
105122
$usersAdminBlock = (new PageCategory())
106123
->setTitle($nameBlock)
@@ -142,4 +159,68 @@ public static function getCategoriesForAdminBlocks(): array
142159
'block-admin-chamilo',
143160
];
144161
}
162+
163+
/**
164+
* Checks if a document file URL is effectively exposed through a visible system announcement.
165+
*
166+
* This centralizes the logic used by different parts of the platform (e.g. voters, controllers)
167+
* to decide if a file coming from personal files can be considered "public" because it is
168+
* embedded inside a system announcement that is visible to the current user.
169+
*
170+
* @param string $pathInfo Full request path (e.g. /r/document/files/{uuid}/view)
171+
* @param string|null $identifier File identifier extracted from the URL (usually a UUID)
172+
* @param UserInterface|null $user Current user, or null to behave as anonymous
173+
* @param string $locale Current locale used to fetch announcements
174+
*/
175+
public function isFilePathExposedByVisibleAnnouncement(
176+
string $pathInfo,
177+
?string $identifier,
178+
?UserInterface $user,
179+
string $locale
180+
): bool {
181+
// Only relax security for the document file viewer route.
182+
if ('' === $pathInfo || !str_contains($pathInfo, '/r/document/files/')) {
183+
return false;
184+
}
185+
186+
// Normalize user: if no authenticated user is provided, behave as anonymous.
187+
if (null === $user) {
188+
$anon = new User();
189+
$anon->setRoles(['ROLE_ANONYMOUS']);
190+
$user = $anon;
191+
}
192+
193+
$accessUrl = $this->accessUrlHelper->getCurrent();
194+
195+
// Fetch announcements that are visible for the given user, URL and locale.
196+
$announcements = $this->sysAnnouncementRepository->getAnnouncements(
197+
$user,
198+
$accessUrl,
199+
$locale
200+
);
201+
202+
foreach ($announcements as $item) {
203+
$content = '';
204+
205+
if (\is_array($item)) {
206+
$content = (string) ($item['content'] ?? '');
207+
} elseif (\is_object($item) && method_exists($item, 'getContent')) {
208+
$content = (string) $item->getContent();
209+
}
210+
211+
if ('' === $content) {
212+
continue;
213+
}
214+
215+
// Check if the announcement HTML contains the viewer path or the identifier.
216+
if (
217+
str_contains($content, $pathInfo)
218+
|| ($identifier && str_contains($content, $identifier))
219+
) {
220+
return true;
221+
}
222+
}
223+
224+
return false;
225+
}
145226
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
<?php
2+
3+
/* For licensing terms, see /license.txt */
4+
5+
declare(strict_types=1);
6+
7+
namespace Chamilo\CoreBundle\Helpers;
8+
9+
use Chamilo\CoreBundle\Entity\ResourceLink;
10+
use Chamilo\CoreBundle\Security\Authorization\Voter\ResourceNodeVoter;
11+
use Laminas\Permissions\Acl\Acl;
12+
use Laminas\Permissions\Acl\Resource\GenericResource;
13+
use Laminas\Permissions\Acl\Role\GenericRole;
14+
use Symfony\Bundle\SecurityBundle\Security;
15+
use Symfony\Component\Security\Acl\Permission\MaskBuilder;
16+
use Symfony\Component\Security\Core\Authentication\Token\NullToken;
17+
use Symfony\Component\Security\Core\User\UserInterface;
18+
19+
readonly class ResourceAclHelper
20+
{
21+
public function __construct(
22+
private Security $security,
23+
) { }
24+
25+
public function isAllowed(
26+
string $attribute,
27+
ResourceLink $resourceLink,
28+
iterable $rights,
29+
bool $allowAnonsToView,
30+
): bool {
31+
// Creating roles
32+
$anon = new GenericRole('IS_AUTHENTICATED_ANONYMOUSLY');
33+
$userRole = new GenericRole('ROLE_USER');
34+
$student = new GenericRole('ROLE_STUDENT');
35+
$teacher = new GenericRole('ROLE_TEACHER');
36+
$studentBoss = new GenericRole('ROLE_STUDENT_BOSS');
37+
38+
$currentStudent = new GenericRole(ResourceNodeVoter::ROLE_CURRENT_COURSE_STUDENT);
39+
$currentTeacher = new GenericRole(ResourceNodeVoter::ROLE_CURRENT_COURSE_TEACHER);
40+
41+
$currentStudentGroup = new GenericRole(ResourceNodeVoter::ROLE_CURRENT_COURSE_GROUP_STUDENT);
42+
$currentTeacherGroup = new GenericRole(ResourceNodeVoter::ROLE_CURRENT_COURSE_GROUP_TEACHER);
43+
44+
$currentStudentSession = new GenericRole(ResourceNodeVoter::ROLE_CURRENT_COURSE_SESSION_STUDENT);
45+
$currentTeacherSession = new GenericRole(ResourceNodeVoter::ROLE_CURRENT_COURSE_SESSION_TEACHER);
46+
47+
// Setting Simple ACL.
48+
$acl = (new Acl())
49+
->addRole($anon)
50+
->addRole($userRole)
51+
->addRole($student)
52+
->addRole($teacher)
53+
->addRole($studentBoss)
54+
55+
->addRole($currentStudent)
56+
->addRole($currentTeacher, ResourceNodeVoter::ROLE_CURRENT_COURSE_STUDENT)
57+
58+
->addRole($currentStudentSession)
59+
->addRole($currentTeacherSession, ResourceNodeVoter::ROLE_CURRENT_COURSE_SESSION_STUDENT)
60+
61+
->addRole($currentStudentGroup)
62+
->addRole($currentTeacherGroup, ResourceNodeVoter::ROLE_CURRENT_COURSE_GROUP_STUDENT)
63+
;
64+
65+
// Add a security resource.
66+
$acl->addResource(new GenericResource((string) $resourceLink->getId()));
67+
68+
// Check all the right this link has.
69+
// Set rights from the ResourceRight.
70+
foreach ($rights as $right) {
71+
$acl->allow($right->getRole(), null, (string) $right->getMask());
72+
}
73+
74+
// Anons can see.
75+
if ($allowAnonsToView) {
76+
$acl->allow($anon, null, (string) ResourceNodeVoter::getReaderMask());
77+
}
78+
79+
// Asked mask
80+
$mask = new MaskBuilder();
81+
$mask->add($attribute);
82+
83+
$askedMask = (string) $mask->get();
84+
85+
if ($this->security->getToken() instanceof NullToken) {
86+
return (bool) $acl->isAllowed('IS_AUTHENTICATED_ANONYMOUSLY', $resourceLink->getId(), $askedMask);
87+
}
88+
89+
$user = $this->security->getUser();
90+
91+
$roles = $user instanceof UserInterface ? $user->getRoles() : [];
92+
93+
foreach ($roles as $role) {
94+
if ($acl->isAllowed($role, $resourceLink->getId(), $askedMask)) {
95+
return true;
96+
}
97+
}
98+
99+
return false;
100+
}
101+
}

src/CoreBundle/Helpers/ResourceFileHelper.php

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,31 +6,37 @@
66

77
namespace Chamilo\CoreBundle\Helpers;
88

9+
use Chamilo\CoreBundle\Entity\AccessUrl;
910
use Chamilo\CoreBundle\Entity\ResourceFile;
1011
use Chamilo\CoreBundle\Entity\ResourceNode;
1112
use Chamilo\CoreBundle\Settings\SettingsManager;
1213

1314
class ResourceFileHelper
1415
{
16+
private bool $accessUrlSpecificFiles;
17+
private ?AccessUrl $currentAccessUrl;
18+
1519
public function __construct(
16-
private readonly SettingsManager $settingsManager,
17-
private readonly AccessUrlHelper $accessUrlHelper,
18-
) {}
20+
SettingsManager $settingsManager,
21+
AccessUrlHelper $accessUrlHelper,
22+
) {
23+
$this->accessUrlSpecificFiles = $accessUrlHelper->isMultiple()
24+
&& 'true' === $settingsManager->getSetting('document.access_url_specific_files');
25+
26+
$this->currentAccessUrl = $accessUrlHelper->getCurrent();
27+
}
1928

2029
public function resolveResourceFileByAccessUrl(ResourceNode $resourceNode): ?ResourceFile
2130
{
2231
if (!$resourceNode->hasResourceFile()) {
2332
return null;
2433
}
2534

26-
$accessUrlSpecificFiles = 'true' === $this->settingsManager->getSetting('document.access_url_specific_files', true)
27-
&& $this->accessUrlHelper->isMultiple();
28-
2935
$resourceFile = null;
3036
$resourceFiles = $resourceNode->getResourceFiles();
3137

32-
if ($accessUrlSpecificFiles) {
33-
$currentUrl = $this->accessUrlHelper->getCurrent()?->getUrl();
38+
if ($this->accessUrlSpecificFiles) {
39+
$currentUrl = $this->currentAccessUrl?->getUrl();
3440

3541
foreach ($resourceFiles as $file) {
3642
if ($file->getAccessUrl() && $file->getAccessUrl()->getUrl() === $currentUrl) {

0 commit comments

Comments
 (0)