Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 85 additions & 4 deletions src/CoreBundle/Helpers/PageHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,34 @@
use Chamilo\CoreBundle\Entity\User;
use Chamilo\CoreBundle\Repository\PageCategoryRepository;
use Chamilo\CoreBundle\Repository\PageRepository;
use Chamilo\CoreBundle\Repository\SysAnnouncementRepository;
use Symfony\Component\Security\Core\User\UserInterface;

class PageHelper
{
protected PageRepository $pageRepository;
protected PageCategoryRepository $pageCategoryRepository;

public function __construct(PageRepository $pageRepository, PageCategoryRepository $pageCategoryRepository)
{
/**
* Repository used to read system announcements (platform news).
*/
protected SysAnnouncementRepository $sysAnnouncementRepository;

/**
* Helper used to retrieve the current AccessUrl.
*/
protected AccessUrlHelper $accessUrlHelper;

public function __construct(
PageRepository $pageRepository,
PageCategoryRepository $pageCategoryRepository,
SysAnnouncementRepository $sysAnnouncementRepository,
AccessUrlHelper $accessUrlHelper
) {
$this->pageRepository = $pageRepository;
$this->pageCategoryRepository = $pageCategoryRepository;
$this->sysAnnouncementRepository = $sysAnnouncementRepository;
$this->accessUrlHelper = $accessUrlHelper;
}

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

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

// Categories for extra content in admin blocks

// Categories for extra content in admin blocks.
foreach (self::getCategoriesForAdminBlocks() as $nameBlock) {
$usersAdminBlock = (new PageCategory())
->setTitle($nameBlock)
Expand Down Expand Up @@ -142,4 +159,68 @@ public static function getCategoriesForAdminBlocks(): array
'block-admin-chamilo',
];
}

/**
* Checks if a document file URL is effectively exposed through a visible system announcement.
*
* This centralizes the logic used by different parts of the platform (e.g. voters, controllers)
* to decide if a file coming from personal files can be considered "public" because it is
* embedded inside a system announcement that is visible to the current user.
*
* @param string $pathInfo Full request path (e.g. /r/document/files/{uuid}/view)
* @param string|null $identifier File identifier extracted from the URL (usually a UUID)
* @param UserInterface|null $user Current user, or null to behave as anonymous
* @param string $locale Current locale used to fetch announcements
*/
public function isFilePathExposedByVisibleAnnouncement(
string $pathInfo,
?string $identifier,
?UserInterface $user,
string $locale
): bool {
// Only relax security for the document file viewer route.
if ('' === $pathInfo || !str_contains($pathInfo, '/r/document/files/')) {
return false;
}

// Normalize user: if no authenticated user is provided, behave as anonymous.
if (null === $user) {
$anon = new User();
$anon->setRoles(['ROLE_ANONYMOUS']);
$user = $anon;
}

$accessUrl = $this->accessUrlHelper->getCurrent();

// Fetch announcements that are visible for the given user, URL and locale.
$announcements = $this->sysAnnouncementRepository->getAnnouncements(
$user,
$accessUrl,
$locale
);

foreach ($announcements as $item) {
$content = '';

if (\is_array($item)) {
$content = (string) ($item['content'] ?? '');
} elseif (\is_object($item) && method_exists($item, 'getContent')) {
$content = (string) $item->getContent();
}

if ('' === $content) {
continue;
}

// Check if the announcement HTML contains the viewer path or the identifier.
if (
str_contains($content, $pathInfo)
|| ($identifier && str_contains($content, $identifier))
) {
return true;
}
}

return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use Chamilo\CoreBundle\Entity\ResourceNode;
use Chamilo\CoreBundle\Entity\ResourceRight;
use Chamilo\CoreBundle\Entity\Session;
use Chamilo\CoreBundle\Helpers\PageHelper;
use Chamilo\CoreBundle\Settings\SettingsManager;
use Chamilo\CourseBundle\Entity\CDocument;
use Chamilo\CourseBundle\Entity\CGroup;
Expand Down Expand Up @@ -51,7 +52,8 @@ public function __construct(
private Security $security,
private RequestStack $requestStack,
private SettingsManager $settingsManager,
private EntityManagerInterface $entityManager
private EntityManagerInterface $entityManager,
private PageHelper $pageHelper,
) {}

public static function getReaderMask(): int
Expand Down Expand Up @@ -117,6 +119,11 @@ protected function voteOnAttribute(string $attribute, $subject, TokenInterface $
return true;
}

// Special case: allow file assets that are embedded inside a visible system announcement.
if (self::VIEW === $attribute && $this->isAnnouncementFileVisibleForCurrentRequest($resourceNode, $token)) {
return true;
}

// @todo
switch ($attribute) {
case self::VIEW:
Expand Down Expand Up @@ -516,6 +523,47 @@ protected function voteOnAttribute(string $attribute, $subject, TokenInterface $
return false;
}

/**
* Checks if the current request is viewing a document file that is embedded
* inside a visible system announcement, delegating the heavy logic to PageHelper.
*/
private function isAnnouncementFileVisibleForCurrentRequest(ResourceNode $resourceNode, TokenInterface $token): bool
{
$type = $resourceNode->getResourceType()?->getTitle();
if ('files' !== $type) {
return false;
}

$request = $this->requestStack->getCurrentRequest();
if (null === $request) {
return false;
}

$pathInfo = (string) $request->getPathInfo();
if ('' === $pathInfo) {
return false;
}

// Extract file identifier from /r/document/files/{identifier}/view.
$segments = explode('/', trim($pathInfo, '/'));
$identifier = null;
if (\count($segments) >= 4) {
// ... /r/document/files/{identifier}/view
$identifier = $segments[\count($segments) - 2] ?? null;
}

$userFromToken = $token->getUser();
$user = $userFromToken instanceof UserInterface ? $userFromToken : null;
$locale = $request->getLocale();

return $this->pageHelper->isFilePathExposedByVisibleAnnouncement(
$pathInfo,
\is_string($identifier) ? $identifier : null,
$user,
$locale
);
}

private function isBlogResource(ResourceNode $node): bool
{
$type = $node->getResourceType()?->getTitle();
Expand Down
Loading