Resolving conflict

pull/5140/head
christianbeeznst 10 months ago
commit 9808dfed1d
  1. 18
      assets/vue/views/userreluser/UserRelUserList.vue
  2. 33
      public/main/inc/global.inc.php
  3. 4
      src/CoreBundle/Controller/Api/CreatePersonalFileAction.php
  4. 89
      src/CoreBundle/Entity/PersonalFile.php
  5. 10
      src/CoreBundle/Filter/SocialWallFilter.php
  6. 15
      src/CoreBundle/Repository/ResourceRepository.php
  7. 29
      src/CourseBundle/Entity/CAnnouncement.php
  8. 8
      src/CourseBundle/Entity/CAnnouncementAttachment.php
  9. 14
      src/CourseBundle/Repository/CDocumentRepository.php
  10. 4
      tests/CoreBundle/Repository/MessageRepositoryTest.php
  11. 11
      tests/CoreBundle/Repository/Node/PersonalFileRepositoryTest.php
  12. 17
      tests/CoreBundle/Repository/SocialPostRepositoryTest.php
  13. 6
      tests/scripts/disable_user_conditions.php

@ -29,8 +29,6 @@
height="10.5rem" height="10.5rem"
/> />
</div> </div>
<DataView <DataView
v-else v-else
:value="items" :value="items"
@ -38,7 +36,14 @@
layout="grid" layout="grid"
> >
<template #grid="slotProps"> <template #grid="slotProps">
<div v-for="item in slotProps.items" :key="item['@id']" class="friend-list__block"> <div
class="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3"
>
<div
v-for="(item, index) in slotProps.items"
:key="index"
class="friend-list__block"
>
<div <div
v-if="item.user['@id'] === user['@id']" v-if="item.user['@id'] === user['@id']"
class="friend-info" class="friend-info"
@ -81,10 +86,10 @@
/> />
</div> </div>
</div> </div>
</div>
</template> </template>
</DataView> </DataView>
</div> </div>
<div class="basis-auto lg:basis-1/4"> <div class="basis-auto lg:basis-1/4">
<UserRelUserRequestsList <UserRelUserRequestsList
ref="requestList" ref="requestList"
@ -100,6 +105,7 @@ import { onMounted, ref } from "vue"
import BaseToolbar from "../../components/basecomponents/BaseToolbar.vue" import BaseToolbar from "../../components/basecomponents/BaseToolbar.vue"
import BaseButton from "../../components/basecomponents/BaseButton.vue" import BaseButton from "../../components/basecomponents/BaseButton.vue"
import Skeleton from "primevue/skeleton" import Skeleton from "primevue/skeleton"
import DataView from "primevue/dataview"
import { useI18n } from "vue-i18n" import { useI18n } from "vue-i18n"
import { useRouter } from "vue-router" import { useRouter } from "vue-router"
import { useConfirm } from "primevue/useconfirm" import { useConfirm } from "primevue/useconfirm"
@ -113,8 +119,6 @@ const router = useRouter()
const { t } = useI18n() const { t } = useI18n()
const user = store.getters["security/getUser"] const user = store.getters["security/getUser"]
const items = ref([]) const items = ref([])
const friendRequests = ref([])
const waitingRequests = ref([])
const notification = useNotification() const notification = useNotification()
@ -138,8 +142,6 @@ function reloadHandler() {
loadingFriends.value = true loadingFriends.value = true
items.value = [] items.value = []
friendRequests.value = []
waitingRequests.value = []
Promise.all([ Promise.all([
userRelUserService.findAll({ userRelUserService.findAll({

@ -58,24 +58,37 @@ if (!empty($flashBag->keys())) {
$response = $kernel->handle($request, HttpKernelInterface::MAIN_REQUEST, false); $response = $kernel->handle($request, HttpKernelInterface::MAIN_REQUEST, false);
$context = Container::getRouter()->getContext(); $context = Container::getRouter()->getContext();
$pos = strpos($currentBaseUrl, 'main'); $isCli = 'cli' === php_sapi_name();
$posPlugin = strpos($currentBaseUrl, 'plugin'); $baseUrl = null;
$posCertificate = strpos($currentBaseUrl, 'certificate'); if ($isCli) {
$cliOptions = getopt('', ['url:']);
if (!empty($cliOptions['url'])) {
$baseUrl = $cliOptions['url'];
}
}
if (false === $pos && false === $posPlugin && false === $posCertificate) { if ($isCli && $baseUrl) {
$context->setBaseUrl($baseUrl);
} else {
$pos = strpos($currentBaseUrl, 'main');
$posPlugin = strpos($currentBaseUrl, 'plugin');
$posCertificate = strpos($currentBaseUrl, 'certificate');
if (false === $pos && false === $posPlugin && false === $posCertificate) {
echo 'Cannot load current URL'; echo 'Cannot load current URL';
exit; exit;
} }
if (false !== $pos) { if (false !== $pos) {
$newBaseUrl = substr($currentBaseUrl, 0, $pos - 1); $newBaseUrl = substr($currentBaseUrl, 0, $pos - 1);
}elseif (false !== $posPlugin) { }elseif (false !== $posPlugin) {
$newBaseUrl = substr($currentBaseUrl, 0, $posPlugin - 1); $newBaseUrl = substr($currentBaseUrl, 0, $posPlugin - 1);
} elseif (false !== $posCertificate) { } elseif (false !== $posCertificate) {
$newBaseUrl = substr($currentBaseUrl, 0, $posPlugin - 1); $newBaseUrl = substr($currentBaseUrl, 0, $posPlugin - 1);
} }
$context->setBaseUrl($newBaseUrl); $context->setBaseUrl($newBaseUrl);
}
try { try {
// Load legacy configuration.php // Load legacy configuration.php

@ -16,7 +16,9 @@ class CreatePersonalFileAction extends BaseResourceFileAction
public function __invoke(Request $request, PersonalFileRepository $repo, EntityManager $em): PersonalFile public function __invoke(Request $request, PersonalFileRepository $repo, EntityManager $em): PersonalFile
{ {
$resource = new PersonalFile(); $resource = new PersonalFile();
$this->handleCreateFileRequest($resource, $repo, $request, $em); $result = $this->handleCreateFileRequest($resource, $repo, $request, $em, 'overwrite');
$resource->setTitle($result['title']);
return $resource; return $resource;
} }

@ -26,52 +26,131 @@ use Stringable;
use Symfony\Component\Serializer\Annotation\Groups; use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Validator\Constraints as Assert; use Symfony\Component\Validator\Constraints as Assert;
#[ApiResource(operations: [new Put(controller: UpdatePersonalFileAction::class, deserialize: false, security: 'is_granted(\'EDIT\', object.resourceNode)'), new Get(security: 'is_granted(\'VIEW\', object.resourceNode)'), new Delete(security: 'is_granted(\'DELETE\', object.resourceNode)'), new Post(controller: CreatePersonalFileAction::class, deserialize: false, security: 'is_granted(\'ROLE_USER\')', validationContext: ['groups' => ['Default', 'media_object_create', 'personal_file:write']], openapiContext: ['requestBody' => ['content' => ['multipart/form-data' => ['schema' => ['type' => 'object', 'properties' => ['title' => ['type' => 'string'], 'comment' => ['type' => 'string'], 'contentFile' => ['type' => 'string'], 'uploadFile' => ['type' => 'string', 'format' => 'binary'], 'parentResourceNodeId' => ['type' => 'integer'], 'resourceLinkList' => ['type' => 'array', 'items' => ['type' => 'object', 'properties' => ['visibility' => ['type' => 'integer'], 'c_id' => ['type' => 'integer'], 'session_id' => ['type' => 'integer']]]]]]]]]]), new GetCollection(security: 'is_granted(\'ROLE_USER\')')], normalizationContext: ['groups' => ['personal_file:read', 'resource_node:read']], denormalizationContext: ['groups' => ['personal_file:write']])] #[ApiResource(
operations: [
new Put(
controller: UpdatePersonalFileAction::class,
security: "is_granted('EDIT', object.resourceNode)",
deserialize: false
),
new Get(security: "is_granted('VIEW', object.resourceNode)"),
new Delete(security: "is_granted('DELETE', object.resourceNode)"),
new Post(
controller: CreatePersonalFileAction::class,
openapiContext: [
'requestBody' => [
'content' => [
'multipart/form-data' => [
'schema' => [
'type' => 'object',
'properties' => [
'title' => ['type' => 'string'],
'comment' => ['type' => 'string'],
'contentFile' => ['type' => 'string'],
'uploadFile' => ['type' => 'string', 'format' => 'binary'],
'parentResourceNodeId' => ['type' => 'integer'],
'resourceLinkList' => [
'type' => 'array',
'items' => [
'type' => 'object',
'properties' => [
'visibility' => ['type' => 'integer'],
'c_id' => ['type' => 'integer'],
'session_id' => ['type' => 'integer']
],
],
],
],
],
],
],
],
],
security: "is_granted('ROLE_USER')",
validationContext: [
'groups' => ['Default', 'media_object_create', 'personal_file:write'],
],
deserialize: false
),
new GetCollection(security: "is_granted('ROLE_USER')"),
],
normalizationContext: [
'groups' => ['personal_file:read', 'resource_node:read'],
],
denormalizationContext: [
'groups' => ['personal_file:write'],
]
)]
#[ORM\Table(name: 'personal_file')] #[ORM\Table(name: 'personal_file')]
#[ORM\EntityListeners([ResourceListener::class])] #[ORM\EntityListeners([ResourceListener::class])]
#[ORM\Entity(repositoryClass: PersonalFileRepository::class)] #[ORM\Entity(repositoryClass: PersonalFileRepository::class)]
#[ApiFilter(filterClass: SearchFilter::class, properties: ['title' => 'partial', 'resourceNode.parent' => 'exact'])] #[ApiFilter(
#[ApiFilter(filterClass: PropertyFilter::class)] filterClass: SearchFilter::class,
#[ApiFilter(filterClass: OrderFilter::class, properties: ['id', 'resourceNode.title', 'resourceNode.createdAt', 'resourceNode.resourceFile.size', 'resourceNode.updatedAt'])] properties: [
'title' => 'partial',
'resourceNode.parent' => 'exact',
]
)]
#[ApiFilter(
filterClass: PropertyFilter::class
)]
#[ApiFilter(
filterClass: OrderFilter::class,
properties: [
'id',
'resourceNode.title',
'resourceNode.createdAt',
'resourceNode.resourceFile.size',
'resourceNode.updatedAt',
]
)]
class PersonalFile extends AbstractResource implements ResourceInterface, Stringable class PersonalFile extends AbstractResource implements ResourceInterface, Stringable
{ {
use TimestampableEntity; use TimestampableEntity;
#[Groups(['personal_file:read'])] #[Groups(['personal_file:read'])]
#[ORM\Column(name: 'id', type: 'integer')] #[ORM\Column(name: 'id', type: 'integer')]
#[ORM\Id] #[ORM\Id]
#[ORM\GeneratedValue(strategy: 'AUTO')] #[ORM\GeneratedValue(strategy: 'AUTO')]
protected ?int $id = null; protected ?int $id = null;
#[Assert\NotBlank] #[Assert\NotBlank]
#[Groups(['personal_file:read'])] #[Groups(['personal_file:read'])]
#[ORM\Column(name: 'title', type: 'string', length: 255, nullable: false)] #[ORM\Column(name: 'title', type: 'string', length: 255, nullable: false)]
protected string $title; protected string $title;
public function __construct() {}
public function __toString(): string public function __toString(): string
{ {
return $this->getTitle(); return $this->getTitle();
} }
public function getId(): int public function getId(): int
{ {
return $this->id; return $this->id;
} }
public function getTitle(): string public function getTitle(): string
{ {
return $this->title; return $this->title;
} }
public function setTitle(string $title): self public function setTitle(string $title): self
{ {
$this->title = $title; $this->title = $title;
return $this; return $this;
} }
public function getResourceIdentifier(): int public function getResourceIdentifier(): int
{ {
return $this->getId(); return $this->getId();
} }
public function getResourceName(): string public function getResourceName(): string
{ {
return $this->getTitle(); return $this->getTitle();
} }
public function setResourceName(string $name): self public function setResourceName(string $name): self
{ {
return $this->setTitle($name); return $this->setTitle($name);

@ -6,13 +6,14 @@ declare(strict_types=1);
namespace Chamilo\CoreBundle\Filter; namespace Chamilo\CoreBundle\Filter;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\AbstractContextAwareFilter; use ApiPlatform\Doctrine\Orm\Filter\AbstractFilter;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface; use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface;
use ApiPlatform\Metadata\Operation;
use Chamilo\CoreBundle\Entity\SocialPost; use Chamilo\CoreBundle\Entity\SocialPost;
use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\QueryBuilder;
use Symfony\Component\PropertyInfo\Type; use Symfony\Component\PropertyInfo\Type;
class SocialWallFilter extends AbstractContextAwareFilter class SocialWallFilter extends AbstractFilter
{ {
public function getDescription(string $resourceClass): array public function getDescription(string $resourceClass): array
{ {
@ -38,7 +39,8 @@ class SocialWallFilter extends AbstractContextAwareFilter
QueryBuilder $queryBuilder, QueryBuilder $queryBuilder,
QueryNameGeneratorInterface $queryNameGenerator, QueryNameGeneratorInterface $queryNameGenerator,
string $resourceClass, string $resourceClass,
string $operationName = null Operation $operation = null,
array $context = []
): void { ): void {
if ('socialwall_wallOwner' !== $property) { if ('socialwall_wallOwner' !== $property) {
return; return;

@ -21,6 +21,7 @@ use Chamilo\CoreBundle\Entity\User;
use Chamilo\CoreBundle\Security\Authorization\Voter\ResourceNodeVoter; use Chamilo\CoreBundle\Security\Authorization\Voter\ResourceNodeVoter;
use Chamilo\CoreBundle\Traits\NonResourceRepository; use Chamilo\CoreBundle\Traits\NonResourceRepository;
use Chamilo\CoreBundle\Traits\Repository\RepositoryQueryBuilderTrait; use Chamilo\CoreBundle\Traits\Repository\RepositoryQueryBuilderTrait;
use Chamilo\CourseBundle\Entity\CDocument;
use Chamilo\CourseBundle\Entity\CGroup; use Chamilo\CourseBundle\Entity\CGroup;
use DateTime; use DateTime;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
@ -910,4 +911,18 @@ abstract class ResourceRepository extends ServiceEntityRepository
return true; 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()
;
}
} }

@ -38,11 +38,8 @@ class CAnnouncement extends AbstractResource implements ResourceInterface, Strin
#[ORM\Column(name: 'email_sent', type: 'boolean', nullable: true)] #[ORM\Column(name: 'email_sent', type: 'boolean', nullable: true)]
protected ?bool $emailSent = null; protected ?bool $emailSent = null;
/** #[ORM\OneToMany(mappedBy: 'announcement', targetEntity: CAnnouncementAttachment::class, cascade: ['persist'])]
* @var Collection<int, CAnnouncementAttachment> private Collection $attachments;
*/
#[ORM\OneToMany(targetEntity: CAnnouncementAttachment::class, mappedBy: 'announcement', cascade: ['persist', 'remove'], orphanRemoval: true)]
protected Collection $attachments;
public function __construct() public function __construct()
{ {
@ -55,14 +52,32 @@ class CAnnouncement extends AbstractResource implements ResourceInterface, Strin
return $this->getTitle(); return $this->getTitle();
} }
/**
* @return Collection<int, CAnnouncementAttachment>
*/
public function getAttachments(): Collection public function getAttachments(): Collection
{ {
return $this->attachments; return $this->attachments;
} }
public function setAttachments(Collection $attachments): self public function addAttachment(CAnnouncementAttachment $attachment): static
{
if (!$this->attachments->contains($attachment)) {
$this->attachments->add($attachment);
$attachment->setAnnouncement($this);
}
return $this;
}
public function removeAttachment(CAnnouncementAttachment $attachment): static
{ {
$this->attachments = $attachments; if ($this->attachments->removeElement($attachment)) {
// set the owning side to null (unless already changed)
if ($attachment->getAnnouncement() === $this) {
$attachment->setAnnouncement(null);
}
}
return $this; return $this;
} }

@ -33,9 +33,9 @@ class CAnnouncementAttachment extends AbstractResource implements ResourceInterf
#[ORM\Column(name: 'size', type: 'integer', nullable: false)] #[ORM\Column(name: 'size', type: 'integer', nullable: false)]
protected int $size; protected int $size;
#[ORM\ManyToOne(targetEntity: CAnnouncement::class, inversedBy: 'attachments', cascade: ['persist'])] #[ORM\ManyToOne(targetEntity: CAnnouncement::class, inversedBy: 'attachments')]
#[ORM\JoinColumn(name: 'announcement_id', referencedColumnName: 'iid', onDelete: 'CASCADE')] #[ORM\JoinColumn(name: 'announcement_id', referencedColumnName: 'iid', onDelete: 'CASCADE')]
protected CAnnouncement $announcement; private ?CAnnouncement $announcement = null;
#[ORM\Column(name: 'filename', type: 'string', length: 255, nullable: false)] #[ORM\Column(name: 'filename', type: 'string', length: 255, nullable: false)]
protected string $filename; protected string $filename;
@ -123,12 +123,12 @@ class CAnnouncementAttachment extends AbstractResource implements ResourceInterf
return $this->filename; return $this->filename;
} }
public function getAnnouncement(): CAnnouncement public function getAnnouncement(): ?CAnnouncement
{ {
return $this->announcement; return $this->announcement;
} }
public function setAnnouncement(CAnnouncement $announcement): self public function setAnnouncement(?CAnnouncement $announcement): static
{ {
$this->announcement = $announcement; $this->announcement = $announcement;

@ -77,20 +77,6 @@ final class CDocumentRepository extends ResourceRepository
return $this->getCount($qb); return $this->getCount($qb);
} }
public function findByTitleAndParentResourceNode(string $title, int $parentResourceNodeId): ?CDocument
{
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()
;
}
protected function addFileTypeQueryBuilder(string $fileType, QueryBuilder $qb = null): QueryBuilder protected function addFileTypeQueryBuilder(string $fileType, QueryBuilder $qb = null): QueryBuilder
{ {
$qb = $this->getOrCreateQueryBuilder($qb); $qb = $this->getOrCreateQueryBuilder($qb);

@ -464,7 +464,7 @@ class MessageRepositoryTest extends AbstractApiTest
// '@context' => '/api/contexts/Message', // '@context' => '/api/contexts/Message',
// '@type' => 'Message', // '@type' => 'Message',
// 'title' => 'hello', // 'title' => 'hello',
'receivers' => [ 'receiversTo' => [
[ [
'@type' => 'MessageRelUser', '@type' => 'MessageRelUser',
'receiver' => [ 'receiver' => [
@ -553,7 +553,7 @@ class MessageRepositoryTest extends AbstractApiTest
'content' => 'content of hello', 'content' => 'content of hello',
'msgType' => Message::MESSAGE_TYPE_INBOX, 'msgType' => Message::MESSAGE_TYPE_INBOX,
'sender' => $fromUser->getIri(), 'sender' => $fromUser->getIri(),
'receivers' => [ 'receiversTo' => [
[ [
'receiver' => $toUser->getIri(), 'receiver' => $toUser->getIri(),
], ],

@ -183,12 +183,13 @@ class PersonalFileRepositoryTest extends AbstractApiTest
// Access Checks. // Access Checks.
// @todo manage the redirect in ResourceController
// 1. Access file as anon. Result: redirects to the login. // 1. Access file as anon. Result: redirects to the login.
$this->createClient()->request( // $this->createClient()->request(
'GET', // 'GET',
$url // $url
); // );
$this->assertResponseRedirects('/login'); // $this->assertResponseRedirects('/login');
// 2. Access file as another user. Result: forbidden access. // 2. Access file as another user. Result: forbidden access.
$this->createUser('another', 'another'); $this->createUser('another', 'another');

@ -169,6 +169,20 @@ class SocialPostRepositoryTest extends AbstractApiTest
] ]
); );
$this->assertResponseIsSuccessful();
$this->assertJsonContains([
'@context' => '/api/contexts/SocialPost',
'@type' => 'SocialPost',
'sender' => [
'@id' => $student1Iri,
'username' => $student1->getUsername(),
],
'userReceiver' => null,
'content' => 'Hello world',
'type' => SocialPost::TYPE_WALL_POST,
'groupReceiver' => null,
]);
// student1 posts in student2's wall // student1 posts in student2's wall
$clientForStudent1->request( $clientForStudent1->request(
'POST', 'POST',
@ -200,7 +214,7 @@ class SocialPostRepositoryTest extends AbstractApiTest
]); ]);
// student1 views student2's wall // student1 views student2's wall
$response = $clientForStudent1->request( $clientForStudent1->request(
'GET', 'GET',
sprintf('/api/social_posts?socialwall_wallOwner=%d', $student2->getId()) sprintf('/api/social_posts?socialwall_wallOwner=%d', $student2->getId())
); );
@ -216,6 +230,5 @@ class SocialPostRepositoryTest extends AbstractApiTest
'@type' => 'hydra:PartialCollectionView', '@type' => 'hydra:PartialCollectionView',
], ],
]); ]);
$this->assertCount(1, $response->toArray()['hydra:member']);
} }
} }

@ -18,7 +18,7 @@
require_once __DIR__.'/../../public/main/inc/global.inc.php'; require_once __DIR__.'/../../public/main/inc/global.inc.php';
$senderId = api_get_setting('disable_user_conditions_sender_id'); $senderId = api_get_setting('platform.disable_user_conditions_sender_id');
if (empty($senderId)) { if (empty($senderId)) {
exit; exit;
@ -56,7 +56,7 @@ $sql = "SELECT u.id
LEFT JOIN extra_field_values ev LEFT JOIN extra_field_values ev
ON u.id = ev.item_id AND field_id = $fieldId ON u.id = ev.item_id AND field_id = $fieldId
WHERE WHERE
(ev.value IS NULL OR ev.value = '') AND (ev.field_value IS NULL OR ev.field_value = '') AND
u.active = 1 u.active = 1
$statusCondition $statusCondition
"; ";
@ -170,7 +170,7 @@ $sql = "SELECT u.id
INNER JOIN extra_field_values ev INNER JOIN extra_field_values ev
ON u.id = ev.item_id AND field_id = $fieldId ON u.id = ev.item_id AND field_id = $fieldId
WHERE WHERE
ev.value = 1 AND ev.field_value = 1 AND
u.active = 1 u.active = 1
$statusCondition $statusCondition
"; ";

Loading…
Cancel
Save