Chamilo is a learning management system focused on ease of use and accessibility
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
chamilo-lms/src/CoreBundle/Repository/ResourceRepository.php

955 lines
30 KiB

<?php
/* For licensing terms, see /license.txt */
10 months ago
declare(strict_types=1);
namespace Chamilo\CoreBundle\Repository;
use Chamilo\CoreBundle\Component\Utils\CreateUploadedFile;
use Chamilo\CoreBundle\Entity\AbstractResource;
use Chamilo\CoreBundle\Entity\Course;
use Chamilo\CoreBundle\Entity\ResourceFile;
use Chamilo\CoreBundle\Entity\ResourceInterface;
use Chamilo\CoreBundle\Entity\ResourceLink;
use Chamilo\CoreBundle\Entity\ResourceNode;
use Chamilo\CoreBundle\Entity\ResourceRight;
use Chamilo\CoreBundle\Entity\ResourceShowCourseResourcesInSessionInterface;
use Chamilo\CoreBundle\Entity\ResourceType;
use Chamilo\CoreBundle\Entity\Session;
use Chamilo\CoreBundle\Entity\User;
use Chamilo\CoreBundle\Security\Authorization\Voter\ResourceNodeVoter;
use Chamilo\CoreBundle\Traits\NonResourceRepository;
use Chamilo\CoreBundle\Traits\Repository\RepositoryQueryBuilderTrait;
use Chamilo\CourseBundle\Entity\CDocument;
use Chamilo\CourseBundle\Entity\CGroup;
use DateTime;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\QueryBuilder;
use Exception;
use LogicException;
use Symfony\Component\Filesystem\Exception\FileNotFoundException;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Throwable;
use const PATHINFO_EXTENSION;
/**
* Extends Resource EntityRepository.
*/
abstract class ResourceRepository extends ServiceEntityRepository
{
use NonResourceRepository;
use RepositoryQueryBuilderTrait;
protected ?ResourceType $resourceType = null;
public function getCount(QueryBuilder $qb): int
{
$qb
->select('count(resource)')
->setMaxResults(1)
->setFirstResult(null)
;
return (int) $qb->getQuery()->getSingleScalarResult();
}
public function getResourceByResourceNode(ResourceNode $resourceNode): ?ResourceInterface
{
return $this->findOneBy([
'resourceNode' => $resourceNode,
]);
}
public function create(AbstractResource $resource): void
{
$this->getEntityManager()->persist($resource);
$this->getEntityManager()->flush();
}
public function update(AbstractResource|User $resource, bool $andFlush = true): void
{
if (!$resource->hasResourceNode()) {
throw new Exception('Resource needs a resource node');
}
$em = $this->getEntityManager();
$resource->getResourceNode()->setUpdatedAt(new DateTime());
$resource->getResourceNode()->setTitle($resource->getResourceName());
$em->persist($resource);
if ($andFlush) {
$em->flush();
}
}
public function updateNodeForResource(ResourceInterface $resource): ResourceNode
{
$em = $this->getEntityManager();
$resourceNode = $resource->getResourceNode();
$resourceName = $resource->getResourceName();
foreach ($resourceNode->getResourceFiles() as $resourceFile) {
if (null !== $resourceFile) {
$originalName = $resourceFile->getOriginalName();
$originalExtension = pathinfo($originalName, PATHINFO_EXTENSION);
// $originalBasename = \basename($resourceName, $originalExtension);
/*$slug = sprintf(
'%s.%s',
$this->slugify->slugify($originalBasename),
$this->slugify->slugify($originalExtension)
);*/
$newOriginalName = \sprintf('%s.%s', $resourceName, $originalExtension);
$resourceFile->setOriginalName($newOriginalName);
$em->persist($resourceFile);
}
}
// $slug = $this->slugify->slugify($resourceName);
$resourceNode->setTitle($resourceName);
// $resourceNode->setSlug($slug);
$em->persist($resourceNode);
$em->persist($resource);
$em->flush();
return $resourceNode;
}
public function findCourseResourceByTitle(
string $title,
ResourceNode $parentNode,
Course $course,
?Session $session = null,
?CGroup $group = null
): ?ResourceInterface {
$qb = $this->getResourcesByCourse($course, $session, $group, $parentNode);
$this->addTitleQueryBuilder($title, $qb);
$qb->setMaxResults(1);
return $qb->getQuery()->getOneOrNullResult();
}
public function findCourseResourceBySlug(
string $title,
ResourceNode $parentNode,
Course $course,
?Session $session = null,
?CGroup $group = null
): ?ResourceInterface {
$qb = $this->getResourcesByCourse($course, $session, $group, $parentNode);
$this->addSlugQueryBuilder($title, $qb);
$qb->setMaxResults(1);
return $qb->getQuery()->getOneOrNullResult();
}
/**
* Find resources ignoring the visibility.
*/
public function findCourseResourceBySlugIgnoreVisibility(
string $title,
ResourceNode $parentNode,
Course $course,
?Session $session = null,
?CGroup $group = null
): ?ResourceInterface {
$qb = $this->getResourcesByCourseIgnoreVisibility($course, $session, $group, $parentNode);
$this->addSlugQueryBuilder($title, $qb);
$qb->setMaxResults(1);
return $qb->getQuery()->getOneOrNullResult();
}
/**
* @return ResourceInterface[]
*/
public function findCourseResourcesByTitle(
string $title,
ResourceNode $parentNode,
Course $course,
?Session $session = null,
?CGroup $group = null
) {
$qb = $this->getResourcesByCourse($course, $session, $group, $parentNode);
$this->addTitleQueryBuilder($title, $qb);
return $qb->getQuery()->getResult();
}
/**
* @todo clean path
*/
public function addFileFromPath(ResourceInterface $resource, string $fileName, string $path, bool $flush = true): ?ResourceFile
{
if (!empty($path) && file_exists($path) && !is_dir($path)) {
$mimeType = mime_content_type($path);
$file = new UploadedFile($path, $fileName, $mimeType, null, true);
return $this->addFile($resource, $file, '', $flush);
}
return null;
}
public function addFileFromString(ResourceInterface $resource, string $fileName, string $mimeType, string $content, bool $flush = true): ?ResourceFile
{
$file = CreateUploadedFile::fromString($fileName, $mimeType, $content);
return $this->addFile($resource, $file, '', $flush);
}
public function addFileFromFileRequest(ResourceInterface $resource, string $fileKey, bool $flush = true): ?ResourceFile
{
$request = $this->getRequest();
if ($request->files->has($fileKey)) {
$file = $request->files->get($fileKey);
if (null !== $file) {
$resourceFile = $this->addFile($resource, $file);
if ($flush) {
$this->getEntityManager()->flush();
}
return $resourceFile;
}
}
return null;
}
public function addFile(ResourceInterface $resource, UploadedFile $file, string $description = '', bool $flush = false): ?ResourceFile
{
$resourceNode = $resource->getResourceNode();
if (null === $resourceNode) {
throw new LogicException('Resource node is null');
}
$em = $this->getEntityManager();
$resourceFile = new ResourceFile();
$resourceFile
->setFile($file)
->setDescription($description)
->setTitle($resource->getResourceName())
->setResourceNode($resourceNode)
;
$resourceNode->addResourceFile($resourceFile);
$em->persist($resourceNode);
if ($flush) {
$em->flush();
}
return $resourceFile;
}
public function getResourceType(): ResourceType
{
$resourceTypeName = $this->toolChain->getResourceTypeNameByEntity($this->getClassName());
$repo = $this->getEntityManager()->getRepository(ResourceType::class);
return $repo->findOneBy([
'title' => $resourceTypeName,
]);
}
public function addVisibilityQueryBuilder(?QueryBuilder $qb = null, bool $checkStudentView = false, bool $displayOnlyPublished = true): QueryBuilder
{
$qb = $this->getOrCreateQueryBuilder($qb);
Internal: PHPUnit: Unit test tweaks * Follow ExtraFieldValues changes on test After a6857b0c30a0561d5186f61ea58be3663b097507 the method and the property changed, so the test also needs to be updated. * Follow UserGroup::setAuthorId() more strict signature on CourseVoterTest It changed on cda50ef101b99b1b166ee28bf38d3bdc53d4e0e3, so test should follow that. * Mock requests stack on CourseVoterTest * Set required CAttendanceCalendar::blocked from CAttendanceRepositoryTest Structure changed at e5397dfaa260bbbd59b8cf30b8fc3c496dcdb582. * Set CAttendanceSheet::signature from CAttendanceRepositoryTest * Mock assumed request on CDocumentRepositoryTest Also, add a note on the session data retrieval point, so it will be replaced for an injection of a request stack object instead. * Temporarily skip a few 403 checks around CDocumentRepositoryTest Let us bring them back soon, but that may need actual fixes to the codebase, so let us postpone them a bit for later. * Make sure assumed server global key is set for CDocumentRepositoryTest This likely needs to be changed to use the value from the symfony request object instead of from the global variable. * Add request mock on CForumCategoryRepositoryTest Ideally it request is injected there, and then the test mocks the request. * Skip forum auto-removal prevention check on category removal This is now working differently on the implementation, so it should be brought back one the implementation prevents the removal, currently it does not prevent it. * Skip forum auto-removal prevention check on post removal Similar to 52f9e663f551580d8af04ecd84740e744fac46a1, see commit message there. * Unify request mocking for a few tests There is enough repetitions already to justify the generalization. It was tempting to use a different trait, but for convenience just used ChamiloTestTrait. * Add mocked request to a couple more tests Namely CCourseDescriptionRepositoryTest, and CForumThreadRepositoryTest. * Skip on-delete check temporarily Again, source needs changing for on-delete cascade behavior before this check can work again. * Add a mocked request for CGroupRepositoryTest * Skip on delete temporarily Again, cascade delete happening but not expected. * Add a mocked request for CLpRepositoryTest * Add mocked request to CQuizRepositoryTest * Skip course visible check temporarily on CQuizRepositoryTest Not working as expected now, it may be an actual problem. * Add a few mocked requests to CStudentPublicationRepositoryTest * Add mocked request to CSurveyRepositoryTest * Adjust commented lines to comply coding standards * Do not set display order on CAnnouncementRepositoryTest Follow 62eaaeda781b366230a6c8a219699a5c746f659d, where the display order is removed from the entity and now on the related resource node. * Do not set display order on CLinkCategoryRepositoryTest Follow 38c0b77c587a4fb13fe8859f0acfb831c4b1e36d, where display order is removed from CLinkCategory in favor of the related resource node equivalent. * Do not set display order on CGlossaryRepositoryTest idem * Do not set display order on CLinkRepositoryTest idem * Make UserRelUserTest pass again When a user A make a friend request to user B, and then user B accepts the request, a new UserRelUser entity is created first with the requested friend constant, and then updated to the friend constant. Only one row is added, and therefore getFriends() method on user A will return user B, but on user B it will not provide any value. Instead when calling getFriendsWithMe() on user A it will provide no values, but on user B it will return A. Adjusted the test to reflect that. Also, tweaked a bit for easier reading. * Make sure assumed server global key is set for PersonalFileRepositoryTest * Change CToolRepositoryTest to focus differently its testing Do not try to use the API for testing the repository.
2 years ago
// TODO Avoid global assumption for a request, and inject
// the request stack instead.
$request = $this->getRequest();
$sessionStudentView = null;
if (null !== $request) {
$sessionStudentView = $request->getSession()->get('studentview');
}
$checker = $this->getAuthorizationChecker();
$isAdminOrTeacher =
$checker->isGranted('ROLE_ADMIN')
|| $checker->isGranted('ROLE_CURRENT_COURSE_TEACHER');
if ($displayOnlyPublished) {
if (!$isAdminOrTeacher
|| ($checkStudentView && 'studentview' === $sessionStudentView)
) {
$qb
->andWhere('links.visibility = :visibility')
->setParameter('visibility', ResourceLink::VISIBILITY_PUBLISHED, Types::INTEGER)
;
}
}
// @todo Add start/end visibility restrictions.
return $qb;
}
public function addCourseQueryBuilder(Course $course, QueryBuilder $qb): QueryBuilder
{
$qb
->andWhere('links.course = :course')
->setParameter('course', $course)
;
return $qb;
}
public function addCourseSessionGroupQueryBuilder(Course $course, ?Session $session = null, ?CGroup $group = null, ?QueryBuilder $qb = null): QueryBuilder
{
$reflectionClass = $this->getClassMetadata()->getReflectionClass();
// Check if this resource type requires to load the base course resources when using a session
$loadBaseSessionContent = \in_array(
ResourceShowCourseResourcesInSessionInterface::class,
$reflectionClass->getInterfaceNames(),
true
);
$this->addCourseQueryBuilder($course, $qb);
if (null === $session) {
$qb->andWhere(
$qb->expr()->orX(
$qb->expr()->isNull('links.session'),
$qb->expr()->eq('links.session', 0)
)
);
} elseif ($loadBaseSessionContent) {
// Load course base content.
$qb->andWhere('links.session = :session OR links.session IS NULL');
$qb->setParameter('session', $session);
} else {
// Load only session resources.
$qb->andWhere('links.session = :session');
$qb->setParameter('session', $session);
}
if (null === $group) {
$qb->andWhere(
$qb->expr()->orX(
$qb->expr()->isNull('links.group'),
$qb->expr()->eq('links.group', 0)
)
);
} else {
$qb->andWhere('links.group = :group');
$qb->setParameter('group', $group);
}
return $qb;
}
public function getResourceTypeName(): string
{
return $this->toolChain->getResourceTypeNameByEntity($this->getClassName());
}
public function getResources(?ResourceNode $parentNode = null): QueryBuilder
{
$resourceTypeName = $this->getResourceTypeName();
$qb = $this->createQueryBuilder('resource')
->select('resource')
->innerJoin('resource.resourceNode', 'node')
->innerJoin('node.resourceLinks', 'links')
->innerJoin('node.resourceType', 'type')
->leftJoin('node.resourceFiles', 'file')
->where('type.title = :type')
->setParameter('type', $resourceTypeName, Types::STRING)
->addSelect('node')
->addSelect('links')
->addSelect('type')
->addSelect('file')
;
if (null !== $parentNode) {
$qb->andWhere('node.parent = :parentNode');
$qb->setParameter('parentNode', $parentNode);
}
return $qb;
}
public function getResourcesByCourse(Course $course, ?Session $session = null, ?CGroup $group = null, ?ResourceNode $parentNode = null, bool $displayOnlyPublished = true, bool $displayOrder = false): QueryBuilder
{
$qb = $this->getResources($parentNode);
$this->addVisibilityQueryBuilder($qb, true, $displayOnlyPublished);
$this->addCourseSessionGroupQueryBuilder($course, $session, $group, $qb);
if ($displayOrder) {
Internal: Move display_order from ResourceNode to ResourceLink (#5143) * Internal: Move display_order from ResourceNode to ResourceLink #5137 * Internal: Use ResourceLink $displayOrder instead of ResourceNode $displayOrder #5137 * Internal: Remove ResourceNode $displayOrder #5137 * Vendor: Bump stof/doctrine-extensions-bundle version to 1.10 #5137 * Forum: Fix query to get forums in category ordered by resourceLink.displayOrder * LP: Refactoring delete method to mark the resource link as deleted instead of remove the lp as resource * LP: Fix query to set a custom order in lp list * LP: Fix buttons to sort learn paths * Rename ResourceNode::getResourceLinkByTypeGroup to ResourceNode::getResourceLinkByContext and change order or its params * LP: Change display resource link display order when moving category position * LP: Fix layout for old list view * Forum: Fix sorting for forums and categories * Course Progress: Fix sorting for sections * Internal: Enable SoftDeleteable for ResourceLink entities * Remove ResourceLink::VISIBILITY_DELETED in favor of soft delete * Internal: Refactoring migration about display_order in course tools * LP: Refactoring the category delete method to make a soft delete of the resource link * LP: Refactoring the thematic delete method to make a soft delete of the resource link * LP: Refactoring the forum delete methods to make a soft delete of the resource link * Add shortcut method to remove resource link from its resource * CI: Fix ResourceNodeRepositoryTest by adding a link to the resource node * Internal: Migration: Drop display_order column from c_lp table * Internal: Migration: Drop position column from c_lp_category table * Internal: Migration: Drop cat_order column from c_forum_category table * Internal: Migration: Drop forum_order column from c_forum_forum table * Internal: Migration: Drop display_order column from c_thematic table * Minor: Format code * Announcement: Fix order of the course announcements * Internal: Refactoring migration * Internal: Migration: Drop display_order column from c_glossary table * Refactoring method to move display order of resource links
2 years ago
$qb->orderBy('links.displayOrder', 'ASC');
}
return $qb;
}
public function getResourcesByCourseIgnoreVisibility(Course $course, ?Session $session = null, ?CGroup $group = null, ?ResourceNode $parentNode = null): QueryBuilder
{
$qb = $this->getResources($parentNode);
$this->addCourseSessionGroupQueryBuilder($course, $session, $group, $qb);
return $qb;
}
/**
* Get resources only from the base course.
*/
public function getResourcesByCourseOnly(Course $course, ?ResourceNode $parentNode = null): QueryBuilder
{
$qb = $this->getResources($parentNode);
$this->addCourseQueryBuilder($course, $qb);
$this->addVisibilityQueryBuilder($qb);
$qb->andWhere('links.session IS NULL');
return $qb;
}
public function getResourceByCreatorFromTitle(
string $title,
User $user,
ResourceNode $parentNode
): ?ResourceInterface {
$qb = $this->getResourcesByCreator($user, $parentNode);
$this->addTitleQueryBuilder($title, $qb);
$qb->setMaxResults(1);
return $qb->getQuery()->getOneOrNullResult();
}
public function getResourcesByCreator(User $user, ?ResourceNode $parentNode = null): QueryBuilder
{
$qb = $this->createQueryBuilder('resource')
->select('resource')
->innerJoin('resource.resourceNode', 'node')
;
if (null !== $parentNode) {
$qb->andWhere('node.parent = :parentNode');
$qb->setParameter('parentNode', $parentNode);
}
$this->addCreatorQueryBuilder($user, $qb);
return $qb;
}
public function getResourcesByCourseLinkedToUser(
User $user,
Course $course,
?Session $session = null,
?CGroup $group = null,
?ResourceNode $parentNode = null
): QueryBuilder {
$qb = $this->getResourcesByCourse($course, $session, $group, $parentNode);
$qb->andWhere('node.creator = :user OR (links.user = :user OR links.user IS NULL)');
$qb->setParameter('user', $user);
return $qb;
}
public function getResourcesByLinkedUser(User $user, ?ResourceNode $parentNode = null): QueryBuilder
{
$qb = $this->getResources($parentNode);
$qb
->andWhere('links.user = :user')
->setParameter('user', $user)
;
$this->addVisibilityQueryBuilder($qb);
return $qb;
}
public function getResourceFromResourceNode(int $resourceNodeId): ?ResourceInterface
{
$qb = $this->createQueryBuilder('resource')
->select('resource')
->addSelect('node')
->addSelect('links')
->innerJoin('resource.resourceNode', 'node')
// ->innerJoin('node.creator', 'userCreator')
->leftJoin('node.resourceLinks', 'links')
->where('node.id = :id')
->setParameters([
'id' => $resourceNodeId,
])
;
return $qb->getQuery()->getOneOrNullResult();
}
5 years ago
public function delete(ResourceInterface $resource): void
{
$em = $this->getEntityManager();
$children = $resource->getResourceNode()->getChildren();
foreach ($children as $child) {
foreach ($child->getResourceFiles() as $resourceFile) {
$em->remove($resourceFile);
}
$resourceNode = $this->getResourceFromResourceNode($child->getId());
if (null !== $resourceNode) {
$this->delete($resourceNode);
}
}
$em->remove($resource);
$em->flush();
}
/**
* Deletes several entities: AbstractResource (Ex: CDocument, CQuiz), ResourceNode,
* ResourceLinks and ResourceFile (including files via Flysystem).
*/
public function hardDelete(AbstractResource $resource): void
{
$em = $this->getEntityManager();
$em->remove($resource);
$em->flush();
}
public function getResourceFileContent(AbstractResource $resource): string
{
try {
$resourceNode = $resource->getResourceNode();
return $this->resourceNodeRepository->getResourceNodeFileContent($resourceNode);
} catch (Throwable $throwable) {
throw new FileNotFoundException($resource->getResourceName());
}
}
public function getResourceNodeFileContent(ResourceNode $resourceNode): string
{
return $this->resourceNodeRepository->getResourceNodeFileContent($resourceNode);
}
/**
* @return false|resource
*/
public function getResourceNodeFileStream(ResourceNode $resourceNode)
{
return $this->resourceNodeRepository->getResourceNodeFileStream($resourceNode);
}
public function getResourceFileDownloadUrl(AbstractResource $resource, array $extraParams = [], ?int $referenceType = null): string
{
$extraParams['mode'] = 'download';
return $this->getResourceFileUrl($resource, $extraParams, $referenceType);
}
public function getResourceFileUrl(AbstractResource $resource, array $extraParams = [], ?int $referenceType = null): string
{
return $this->getResourceNodeRepository()->getResourceFileUrl(
$resource->getResourceNode(),
$extraParams,
$referenceType
);
}
public function updateResourceFileContent(AbstractResource $resource, string $content): bool
{
$resourceNode = $resource->getResourceNode();
if ($resourceNode->hasResourceFile()) {
$resourceNode->setContent($content);
foreach ($resourceNode->getResourceFiles() as $resourceFile) {
$resourceFile->setSize(\strlen($content));
}
return true;
}
return false;
}
public function setResourceName(AbstractResource $resource, $title): void
{
if (!empty($title)) {
$resource->setResourceName($title);
$resourceNode = $resource->getResourceNode();
$resourceNode->setTitle($title);
}
}
public function toggleVisibilityPublishedDraft(
AbstractResource $resource,
?Course $course = null,
?Session $session = null
): void {
$firstLink = $resource->getFirstResourceLink();
if (ResourceLink::VISIBILITY_PUBLISHED === $firstLink->getVisibility()) {
$this->setVisibilityDraft($resource, $course, $session);
return;
}
if (ResourceLink::VISIBILITY_DRAFT === $firstLink->getVisibility()) {
$this->setVisibilityPublished($resource, $course, $session);
}
}
public function setVisibilityPublished(
AbstractResource $resource,
?Course $course = null,
?Session $session = null,
): void {
$this->setLinkVisibility($resource, ResourceLink::VISIBILITY_PUBLISHED, true, $course, $session);
}
public function setVisibilityDraft(
AbstractResource $resource,
?Course $course = null,
?Session $session = null,
): void {
$this->setLinkVisibility($resource, ResourceLink::VISIBILITY_DRAFT, true, $course, $session);
}
public function setVisibilityPending(
AbstractResource $resource,
?Course $course = null,
?Session $session = null,
): void {
$this->setLinkVisibility($resource, ResourceLink::VISIBILITY_PENDING, true, $course, $session);
}
public function addResourceNode(
ResourceInterface $resource,
User $creator,
ResourceInterface $parentResource,
?ResourceType $resourceType = null,
): ResourceNode {
$parentResourceNode = $parentResource->getResourceNode();
return $this->createNodeForResource(
$resource,
$creator,
$parentResourceNode,
null,
$resourceType,
);
}
/**
* @todo remove this function and merge it with addResourceNode()
*/
public function createNodeForResource(
ResourceInterface $resource,
User $creator,
ResourceNode $parentNode,
?UploadedFile $file = null,
?ResourceType $resourceType = null,
): ResourceNode {
$em = $this->getEntityManager();
$resourceType = $resourceType ?: $this->getResourceType();
$resourceName = $resource->getResourceName();
$extension = $this->slugify->slugify(pathinfo($resourceName, PATHINFO_EXTENSION));
if (empty($extension)) {
$slug = $this->slugify->slugify($resourceName);
} else {
$originalExtension = pathinfo($resourceName, PATHINFO_EXTENSION);
$originalBasename = basename($resourceName, $originalExtension);
$slug = \sprintf('%s.%s', $this->slugify->slugify($originalBasename), $originalExtension);
}
$resourceNode = new ResourceNode();
$resourceNode
->setTitle($resourceName)
->setSlug($slug)
->setResourceType($resourceType)
;
$creator->addResourceNode($resourceNode);
$parentNode?->addChild($resourceNode);
$resource->setResourceNode($resourceNode);
$em->persist($resourceNode);
$em->persist($resource);
if (null !== $file) {
$this->addFile($resource, $file);
}
return $resourceNode;
}
/**
* This is only used during installation for the special nodes (admin and AccessUrl).
*/
public function createNodeForResourceWithNoParent(ResourceInterface $resource, User $creator): ResourceNode
{
$em = $this->getEntityManager();
$resourceType = $this->getResourceType();
$resourceName = $resource->getResourceName();
$slug = $this->slugify->slugify($resourceName);
$resourceNode = new ResourceNode();
$resourceNode
->setTitle($resourceName)
->setSlug($slug)
->setCreator($creator)
->setResourceType($resourceType)
;
$resource->setResourceNode($resourceNode);
$em->persist($resourceNode);
$em->persist($resource);
return $resourceNode;
}
public function getTotalSpaceByCourse(Course $course, ?CGroup $group = null, ?Session $session = null): int
{
$qb = $this->createQueryBuilder('resource');
$qb
->select('SUM(file.size) as total')
->innerJoin('resource.resourceNode', 'node')
->innerJoin('node.resourceLinks', 'l')
->innerJoin('node.resourceFiles', 'file')
->where('l.course = :course')
->andWhere('file IS NOT NULL')
->setParameters(
[
'course' => $course,
]
)
;
if (null === $group) {
$qb->andWhere('l.group IS NULL');
} else {
$qb
->andWhere('l.group = :group')
->setParameter('group', $group)
;
}
if (null === $session) {
$qb->andWhere('l.session IS NULL');
} else {
$qb
->andWhere('l.session = :session')
->setParameter('session', $session)
;
}
$query = $qb->getQuery();
return (int) $query->getSingleScalarResult();
}
public function addTitleDecoration(AbstractResource $resource, Course $course, ?Session $session = null): string
{
if (null === $session) {
return '';
}
$link = $resource->getFirstResourceLinkFromCourseSession($course, $session);
if (null === $link) {
return '';
}
return '<img title="'.$session->getTitle().'" src="/img/icons/22/star.png" />';
}
public function isGranted(string $subject, AbstractResource $resource): bool
{
return $this->getAuthorizationChecker()->isGranted($subject, $resource->getResourceNode());
}
/**
* Changes the visibility of the children that matches the exact same link.
*/
public function copyVisibilityToChildren(ResourceNode $resourceNode, ResourceLink $link): bool
{
$children = $resourceNode->getChildren();
if (0 === $children->count()) {
return false;
}
$em = $this->getEntityManager();
/** @var ResourceNode $child */
foreach ($children as $child) {
if ($child->getChildren()->count() > 0) {
$this->copyVisibilityToChildren($child, $link);
}
$links = $child->getResourceLinks();
foreach ($links as $linkItem) {
if ($linkItem->getUser() === $link->getUser()
&& $linkItem->getSession() === $link->getSession()
&& $linkItem->getCourse() === $link->getCourse()
&& $linkItem->getUserGroup() === $link->getUserGroup()
) {
$linkItem->setVisibility($link->getVisibility());
$em->persist($linkItem);
}
}
}
$em->flush();
return true;
}
protected function addSlugQueryBuilder(?string $slug, ?QueryBuilder $qb = null): QueryBuilder
{
$qb = $this->getOrCreateQueryBuilder($qb);
if (null === $slug) {
return $qb;
}
$qb
->andWhere('node.slug = :slug OR node.slug LIKE :slug2')
->setParameter('slug', $slug) // normal slug = title
->setParameter('slug2', $slug.'%-%') // slug with a counter = title-1
;
return $qb;
}
protected function addTitleQueryBuilder(?string $title, ?QueryBuilder $qb = null): QueryBuilder
{
$qb = $this->getOrCreateQueryBuilder($qb);
if (null === $title) {
return $qb;
}
$qb
->andWhere('node.title = :title')
->setParameter('title', $title)
;
return $qb;
}
protected function addCreatorQueryBuilder(?User $user, ?QueryBuilder $qb = null): QueryBuilder
{
$qb = $this->getOrCreateQueryBuilder($qb);
if (null === $user) {
return $qb;
}
$qb
->andWhere('node.creator = :creator')
->setParameter('creator', $user)
;
return $qb;
}
private function setLinkVisibility(
AbstractResource $resource,
int $visibility,
bool $recursive = true,
?Course $course = null,
?Session $session = null,
?CGroup $group = null,
?User $user = null,
): bool {
$resourceNode = $resource->getResourceNode();
if (null === $resourceNode) {
return false;
}
$em = $this->getEntityManager();
if ($recursive) {
$children = $resourceNode->getChildren();
/** @var ResourceNode $child */
foreach ($children as $child) {
$criteria = [
'resourceNode' => $child,
];
$childDocument = $this->findOneBy($criteria);
if ($childDocument) {
$this->setLinkVisibility($childDocument, $visibility);
}
}
}
if ($resource instanceof ResourceShowCourseResourcesInSessionInterface) {
$link = $resource->getFirstResourceLinkFromCourseSession($course, $session);
if (!$link) {
$resource->parentResource = $course;
$resource->addCourseLink($course, $session);
}
$link = $resource->getFirstResourceLinkFromCourseSession($course, $session);
$links = [$link];
} else {
$links = $resourceNode->getResourceLinks();
}
/** @var ResourceLink $link */
foreach ($links as $link) {
$link->setVisibility($visibility);
if (ResourceLink::VISIBILITY_DRAFT === $visibility) {
$editorMask = ResourceNodeVoter::getEditorMask();
$resourceRight = (new ResourceRight())
->setMask($editorMask)
->setRole(ResourceNodeVoter::ROLE_CURRENT_COURSE_TEACHER)
->setResourceLink($link)
;
$link->addResourceRight($resourceRight);
} else {
$link->setResourceRights(new ArrayCollection());
}
$em->persist($link);
}
$em->flush();
return true;
}
public function findByTitleAndParentResourceNode(string $title, int $parentResourceNodeId): ?AbstractResource
{
return $this->createQueryBuilder('d')
->innerJoin('d.resourceNode', 'node')
->andWhere('d.title = :title')
->andWhere('node.parent = :parentResourceNodeId')
->setParameter('title', $title)
->setParameter('parentResourceNodeId', $parentResourceNodeId)
->setMaxResults(1)
->getQuery()
->getOneOrNullResult()
;
}
public function findResourceByTitleInCourse(
string $title,
Course $course,
?Session $session = null,
?CGroup $group = null
): ?ResourceInterface {
$qb = $this->getResourcesByCourse($course, $session, $group);
$this->addTitleQueryBuilder($title, $qb);
$qb->setMaxResults(1);
return $qb->getQuery()->getOneOrNullResult();
}
}