chamilo /
chamilo-lms
| 1 | <?php |
||
| 2 | |||
| 3 | declare(strict_types=1); |
||
| 4 | |||
| 5 | /* For licensing terms, see /license.txt */ |
||
| 6 | |||
| 7 | namespace Chamilo\CourseBundle\Entity; |
||
| 8 | |||
| 9 | use ApiPlatform\Doctrine\Orm\Filter\OrderFilter; |
||
| 10 | use ApiPlatform\Doctrine\Orm\Filter\SearchFilter; |
||
| 11 | use ApiPlatform\Metadata\ApiFilter; |
||
| 12 | use ApiPlatform\Metadata\ApiProperty; |
||
| 13 | use ApiPlatform\Metadata\ApiResource; |
||
| 14 | use ApiPlatform\Metadata\Delete; |
||
| 15 | use ApiPlatform\Metadata\Get; |
||
| 16 | use ApiPlatform\Metadata\GetCollection; |
||
| 17 | use ApiPlatform\Metadata\Post; |
||
| 18 | use ApiPlatform\Metadata\Put; |
||
| 19 | use ApiPlatform\OpenApi\Model\Operation; |
||
| 20 | use ApiPlatform\OpenApi\Model\Parameter; |
||
| 21 | use ApiPlatform\OpenApi\Model\RequestBody; |
||
| 22 | use ApiPlatform\Serializer\Filter\PropertyFilter; |
||
| 23 | use ArrayObject; |
||
| 24 | use Chamilo\CoreBundle\Controller\Api\CreateDocumentFileAction; |
||
| 25 | use Chamilo\CoreBundle\Controller\Api\DocumentLearningPathUsageAction; |
||
| 26 | use Chamilo\CoreBundle\Controller\Api\DownloadSelectedDocumentsAction; |
||
| 27 | use Chamilo\CoreBundle\Controller\Api\ReplaceDocumentFileAction; |
||
| 28 | use Chamilo\CoreBundle\Controller\Api\UpdateDocumentFileAction; |
||
| 29 | use Chamilo\CoreBundle\Controller\Api\UpdateVisibilityDocument; |
||
| 30 | use Chamilo\CoreBundle\Entity\AbstractResource; |
||
| 31 | use Chamilo\CoreBundle\Entity\GradebookCategory; |
||
| 32 | use Chamilo\CoreBundle\Entity\Listener\ResourceListener; |
||
| 33 | use Chamilo\CoreBundle\Entity\ResourceInterface; |
||
| 34 | use Chamilo\CoreBundle\Entity\ResourceNode; |
||
| 35 | use Chamilo\CoreBundle\Entity\ResourceShowCourseResourcesInSessionInterface; |
||
| 36 | use Chamilo\CoreBundle\Filter\CidFilter; |
||
| 37 | use Chamilo\CoreBundle\Filter\SidFilter; |
||
| 38 | use Chamilo\CourseBundle\Repository\CDocumentRepository; |
||
| 39 | use Doctrine\Common\Collections\ArrayCollection; |
||
| 40 | use Doctrine\Common\Collections\Collection; |
||
| 41 | use Doctrine\ORM\Mapping as ORM; |
||
| 42 | use Stringable; |
||
| 43 | use Symfony\Component\Serializer\Annotation\Groups; |
||
| 44 | use Symfony\Component\Uid\Uuid; |
||
| 45 | use Symfony\Component\Validator\Constraints as Assert; |
||
| 46 | |||
| 47 | #[ApiResource( |
||
| 48 | shortName: 'Document', |
||
| 49 | operations: [ |
||
| 50 | new Put( |
||
| 51 | controller: UpdateDocumentFileAction::class, |
||
| 52 | security: "is_granted('EDIT', object.resourceNode)", |
||
| 53 | validationContext: [ |
||
| 54 | 'groups' => ['media_object_create', 'document:write'], |
||
| 55 | ], |
||
| 56 | deserialize: false |
||
| 57 | ), |
||
| 58 | new Put( |
||
| 59 | uriTemplate: '/documents/{iid}/toggle_visibility', |
||
| 60 | controller: UpdateVisibilityDocument::class, |
||
| 61 | openapi: new Operation( |
||
| 62 | summary: 'Change document visibility (visible/invisible to learners)' |
||
| 63 | ), |
||
| 64 | security: "is_granted('EDIT', object.resourceNode)", |
||
| 65 | deserialize: false |
||
| 66 | ), |
||
| 67 | new Put( |
||
| 68 | uriTemplate: '/documents/{iid}/move', |
||
| 69 | controller: UpdateDocumentFileAction::class, |
||
| 70 | openapi: new Operation( |
||
| 71 | summary: 'Move document' |
||
| 72 | ), |
||
| 73 | security: "is_granted('EDIT', object.resourceNode)", |
||
| 74 | deserialize: true |
||
| 75 | ), |
||
| 76 | new Post( |
||
| 77 | uriTemplate: '/documents/{iid}/replace', |
||
| 78 | controller: ReplaceDocumentFileAction::class, |
||
| 79 | openapi: new Operation( |
||
| 80 | summary: 'Replace a document file, maintaining the same IDs.', |
||
| 81 | requestBody: new RequestBody( |
||
| 82 | content: new ArrayObject([ |
||
| 83 | 'multipart/form-data' => [ |
||
| 84 | 'schema' => [ |
||
| 85 | 'type' => 'object', |
||
| 86 | 'properties' => [ |
||
| 87 | 'file' => [ |
||
| 88 | 'type' => 'string', |
||
| 89 | 'format' => 'binary', |
||
| 90 | ], |
||
| 91 | ], |
||
| 92 | ], |
||
| 93 | ], |
||
| 94 | ]), |
||
| 95 | ), |
||
| 96 | ), |
||
| 97 | security: "is_granted('ROLE_CURRENT_COURSE_TEACHER') or is_granted('ROLE_CURRENT_COURSE_SESSION_TEACHER') or is_granted('ROLE_TEACHER')", |
||
| 98 | validationContext: ['groups' => ['Default', 'media_object_create', 'document:write']], |
||
| 99 | deserialize: false |
||
| 100 | ), |
||
| 101 | new Get(security: "is_granted('VIEW', object.resourceNode)"), |
||
| 102 | new Get( |
||
| 103 | uriTemplate: '/documents/{iid}/lp-usage', |
||
| 104 | controller: DocumentLearningPathUsageAction::class, |
||
| 105 | openapi: new Operation( |
||
| 106 | summary: 'Get a list of learning paths where a document is used' |
||
| 107 | ), |
||
| 108 | security: "is_granted('ROLE_USER')", |
||
| 109 | read: false, |
||
| 110 | name: 'api_documents_lp_usage' |
||
| 111 | ), |
||
| 112 | new Delete(security: "is_granted('DELETE', object.resourceNode)"), |
||
| 113 | new Post( |
||
| 114 | controller: CreateDocumentFileAction::class, |
||
| 115 | openapi: new Operation( |
||
| 116 | requestBody: new RequestBody( |
||
| 117 | content: new ArrayObject([ |
||
| 118 | 'multipart/form-data' => [ |
||
| 119 | 'schema' => [ |
||
| 120 | 'type' => 'object', |
||
| 121 | 'properties' => [ |
||
| 122 | 'title' => ['type' => 'string'], |
||
| 123 | 'filetype' => [ |
||
| 124 | 'type' => 'string', |
||
| 125 | 'enum' => ['folder', 'file'], |
||
| 126 | ], |
||
| 127 | 'comment' => ['type' => 'string'], |
||
| 128 | 'contentFile' => ['type' => 'string'], |
||
| 129 | 'uploadFile' => [ |
||
| 130 | 'type' => 'string', |
||
| 131 | 'format' => 'binary', |
||
| 132 | ], |
||
| 133 | 'parentResourceNodeId' => ['type' => 'integer'], |
||
| 134 | 'resourceLinkList' => [ |
||
| 135 | 'type' => 'array', |
||
| 136 | 'items' => [ |
||
| 137 | 'type' => 'object', |
||
| 138 | 'properties' => [ |
||
| 139 | 'visibility' => ['type' => 'integer'], |
||
| 140 | 'cid' => ['type' => 'integer'], |
||
| 141 | 'gid' => ['type' => 'integer'], |
||
| 142 | 'sid' => ['type' => 'integer'], |
||
| 143 | ], |
||
| 144 | ], |
||
| 145 | ], |
||
| 146 | 'isUncompressZipEnabled' => ['type' => 'boolean'], |
||
| 147 | 'fileExistsOption' => [ |
||
| 148 | 'type' => 'string', |
||
| 149 | 'enum' => ['overwrite', 'skip', 'rename'], |
||
| 150 | ], |
||
| 151 | ], |
||
| 152 | ], |
||
| 153 | ], |
||
| 154 | ]), |
||
| 155 | ), |
||
| 156 | ), |
||
| 157 | security: "is_granted('ROLE_CURRENT_COURSE_TEACHER') or is_granted('ROLE_CURRENT_COURSE_SESSION_TEACHER') or is_granted('ROLE_TEACHER')", |
||
| 158 | validationContext: ['groups' => ['Default', 'media_object_create', 'document:write']], |
||
| 159 | deserialize: false |
||
| 160 | ), |
||
| 161 | new Post( |
||
| 162 | uriTemplate: '/documents/download-selected', |
||
| 163 | controller: DownloadSelectedDocumentsAction::class, |
||
| 164 | openapi: new Operation( |
||
| 165 | summary: 'Download selected documents as a ZIP file.', |
||
| 166 | requestBody: new RequestBody( |
||
| 167 | content: new ArrayObject([ |
||
| 168 | 'application/json' => [ |
||
| 169 | 'schema' => [ |
||
| 170 | 'type' => 'object', |
||
| 171 | 'properties' => [ |
||
| 172 | 'ids' => [ |
||
| 173 | 'type' => 'array', |
||
| 174 | 'items' => ['type' => 'integer'], |
||
| 175 | ], |
||
| 176 | ], |
||
| 177 | ], |
||
| 178 | ], |
||
| 179 | ]), |
||
| 180 | ), |
||
| 181 | ), |
||
| 182 | security: "is_granted('ROLE_USER')", |
||
| 183 | ), |
||
| 184 | new GetCollection( |
||
| 185 | openapi: new Operation( |
||
| 186 | parameters: [ |
||
| 187 | new Parameter( |
||
| 188 | name: 'resourceNode.parent', |
||
| 189 | in: 'query', |
||
| 190 | description: 'Resource node Parent', |
||
| 191 | required: true, |
||
| 192 | schema: ['type' => 'integer'], |
||
| 193 | ), |
||
| 194 | ], |
||
| 195 | ) |
||
| 196 | ), |
||
| 197 | ], |
||
| 198 | normalizationContext: [ |
||
| 199 | 'groups' => ['document:read', 'resource_node:read'], |
||
| 200 | ], |
||
| 201 | denormalizationContext: [ |
||
| 202 | 'groups' => ['document:write'], |
||
| 203 | ] |
||
| 204 | )] |
||
| 205 | #[ORM\Table(name: 'c_document')] |
||
| 206 | #[ORM\Index(columns: ['filetype'], name: 'idx_cdoc_type')] |
||
| 207 | #[ORM\Entity(repositoryClass: CDocumentRepository::class)] |
||
| 208 | #[ORM\EntityListeners([ResourceListener::class])] |
||
| 209 | #[ApiFilter(filterClass: PropertyFilter::class)] |
||
| 210 | #[ApiFilter(filterClass: SearchFilter::class, properties: ['title' => 'partial', 'resourceNode.parent' => 'exact', 'filetype' => 'exact'])] |
||
| 211 | #[ApiFilter( |
||
| 212 | filterClass: OrderFilter::class, |
||
| 213 | properties: [ |
||
| 214 | 'iid', |
||
| 215 | 'filetype', |
||
| 216 | 'resourceNode.title', |
||
| 217 | 'resourceNode.createdAt', |
||
| 218 | 'resourceNode.firstResourceFile.size', |
||
| 219 | 'resourceNode.updatedAt', |
||
| 220 | ] |
||
| 221 | )] |
||
| 222 | #[ApiFilter(filterClass: CidFilter::class)] |
||
| 223 | #[ApiFilter(filterClass: SidFilter::class)] |
||
| 224 | class CDocument extends AbstractResource implements ResourceInterface, ResourceShowCourseResourcesInSessionInterface, Stringable |
||
| 225 | { |
||
| 226 | #[ApiProperty(identifier: true)] |
||
| 227 | #[Groups(['document:read'])] |
||
| 228 | #[ORM\Column(name: 'iid', type: 'integer')] |
||
| 229 | #[ORM\Id] |
||
| 230 | #[ORM\GeneratedValue] |
||
| 231 | protected ?int $iid = null; |
||
| 232 | |||
| 233 | #[Groups(['document:read', 'document:write', 'document:browse', 'student_publication_rel_document:read'])] |
||
| 234 | #[Assert\NotBlank] |
||
| 235 | #[ORM\Column(name: 'title', type: 'string', length: 255, nullable: false)] |
||
| 236 | protected string $title; |
||
| 237 | |||
| 238 | #[Groups(['document:read', 'document:write'])] |
||
| 239 | #[ORM\Column(name: 'comment', type: 'text', nullable: true)] |
||
| 240 | protected ?string $comment; |
||
| 241 | |||
| 242 | #[Groups(['document:read', 'document:write'])] |
||
| 243 | #[Assert\Choice(['folder', 'file', 'certificate', 'video'], message: 'Choose a valid filetype.')] |
||
| 244 | #[ORM\Column(name: 'filetype', type: 'string', length: 15, nullable: false)] |
||
| 245 | protected string $filetype; |
||
| 246 | |||
| 247 | #[ORM\Column(name: 'readonly', type: 'boolean', nullable: false)] |
||
| 248 | protected bool $readonly; |
||
| 249 | |||
| 250 | #[Groups(['document:read', 'document:write'])] |
||
| 251 | #[ORM\Column(name: 'template', type: 'boolean', nullable: false)] |
||
| 252 | protected bool $template; |
||
| 253 | |||
| 254 | #[Groups(['document:read'])] |
||
| 255 | #[ORM\OneToMany(mappedBy: 'document', targetEntity: GradebookCategory::class)] |
||
| 256 | private Collection $gradebookCategories; |
||
| 257 | |||
| 258 | public function __construct() |
||
| 259 | { |
||
| 260 | $this->comment = ''; |
||
| 261 | $this->filetype = 'folder'; |
||
| 262 | $this->readonly = false; |
||
| 263 | $this->template = false; |
||
| 264 | $this->gradebookCategories = new ArrayCollection(); |
||
| 265 | } |
||
| 266 | |||
| 267 | public function __toString(): string |
||
| 268 | { |
||
| 269 | return $this->getTitle(); |
||
| 270 | } |
||
| 271 | |||
| 272 | public function getTitle(): string |
||
| 273 | { |
||
| 274 | return $this->title; |
||
| 275 | } |
||
| 276 | |||
| 277 | public function setTitle(string $title): self |
||
| 278 | { |
||
| 279 | $this->title = $title; |
||
| 280 | |||
| 281 | return $this; |
||
| 282 | } |
||
| 283 | |||
| 284 | public function isTemplate(): bool |
||
| 285 | { |
||
| 286 | return $this->template; |
||
| 287 | } |
||
| 288 | |||
| 289 | public function setTemplate(bool $template): self |
||
| 290 | { |
||
| 291 | $this->template = $template; |
||
| 292 | |||
| 293 | return $this; |
||
| 294 | } |
||
| 295 | |||
| 296 | public function getComment(): ?string |
||
| 297 | { |
||
| 298 | return $this->comment; |
||
| 299 | } |
||
| 300 | |||
| 301 | public function setComment(?string $comment): self |
||
| 302 | { |
||
| 303 | $this->comment = $comment; |
||
| 304 | |||
| 305 | return $this; |
||
| 306 | } |
||
| 307 | |||
| 308 | public function getFiletype(): string |
||
| 309 | { |
||
| 310 | return $this->filetype; |
||
| 311 | } |
||
| 312 | |||
| 313 | public function setFiletype(string $filetype): self |
||
| 314 | { |
||
| 315 | $this->filetype = $filetype; |
||
| 316 | |||
| 317 | return $this; |
||
| 318 | } |
||
| 319 | |||
| 320 | public function getReadonly(): bool |
||
| 321 | { |
||
| 322 | return $this->readonly; |
||
| 323 | } |
||
| 324 | |||
| 325 | public function setReadonly(bool $readonly): self |
||
| 326 | { |
||
| 327 | $this->readonly = $readonly; |
||
| 328 | |||
| 329 | return $this; |
||
| 330 | } |
||
| 331 | |||
| 332 | public function getResourceIdentifier(): int|Uuid |
||
| 333 | { |
||
| 334 | return $this->getIid(); |
||
|
0 ignored issues
–
show
Bug
Best Practice
introduced
by
Loading history...
|
|||
| 335 | } |
||
| 336 | |||
| 337 | public function getIid(): ?int |
||
| 338 | { |
||
| 339 | return $this->iid; |
||
| 340 | } |
||
| 341 | |||
| 342 | public function getResourceName(): string |
||
| 343 | { |
||
| 344 | return $this->getTitle(); |
||
| 345 | } |
||
| 346 | |||
| 347 | public function setResourceName(string $name): self |
||
| 348 | { |
||
| 349 | return $this->setTitle($name); |
||
| 350 | } |
||
| 351 | |||
| 352 | /** |
||
| 353 | * @return Collection<int, GradebookCategory> |
||
| 354 | */ |
||
| 355 | public function getGradebookCategories(): Collection |
||
| 356 | { |
||
| 357 | return $this->gradebookCategories; |
||
| 358 | } |
||
| 359 | |||
| 360 | public function addGradebookCategory(GradebookCategory $gradebookCategory): static |
||
| 361 | { |
||
| 362 | if (!$this->gradebookCategories->contains($gradebookCategory)) { |
||
| 363 | $this->gradebookCategories->add($gradebookCategory); |
||
| 364 | $gradebookCategory->setDocument($this); |
||
| 365 | } |
||
| 366 | |||
| 367 | return $this; |
||
| 368 | } |
||
| 369 | |||
| 370 | public function removeGradebookCategory(GradebookCategory $gradebookCategory): static |
||
| 371 | { |
||
| 372 | if ($this->gradebookCategories->removeElement($gradebookCategory)) { |
||
| 373 | // set the owning side to null (unless already changed) |
||
| 374 | if ($gradebookCategory->getDocument() === $this) { |
||
| 375 | $gradebookCategory->setDocument(null); |
||
| 376 | } |
||
| 377 | } |
||
| 378 | |||
| 379 | return $this; |
||
| 380 | } |
||
| 381 | |||
| 382 | #[Groups(['document:read', 'document:fullPath'])] |
||
| 383 | public function getFullPath(): string |
||
| 384 | { |
||
| 385 | $pathParts = [$this->getTitle()]; |
||
| 386 | |||
| 387 | $parent = $this->getParent(); |
||
| 388 | while ($parent instanceof ResourceNode) { |
||
| 389 | array_unshift($pathParts, $parent->getTitle()); |
||
| 390 | $parent = $parent->getParent(); |
||
| 391 | } |
||
| 392 | |||
| 393 | return implode('/', $pathParts); |
||
| 394 | } |
||
| 395 | } |
||
| 396 |