From a93b06ef21da4cd3cc593c33a3a44558d649a118 Mon Sep 17 00:00:00 2001 From: christian Date: Mon, 3 Jul 2023 15:48:19 -0500 Subject: [PATCH] Glossary: Change from legacy to Vue interface and backend adaptation - refs #4768 --- .../glossary/GlossaryExportForm.vue | 95 +++++++ .../vue/components/glossary/GlossaryForm.vue | 154 +++++++++++ .../glossary/GlossaryImportForm.vue | 127 +++++++++ .../components/glossary/GlossaryLayout.vue | 9 + assets/vue/router/glossary.js | 34 +++ assets/vue/router/index.js | 2 + assets/vue/views/glossary/CreateTerm.vue | 35 +++ assets/vue/views/glossary/ExportGlossary.vue | 41 +++ assets/vue/views/glossary/GlossaryList.vue | 258 ++++++++++++++++++ assets/vue/views/glossary/ImportGlossary.vue | 35 +++ assets/vue/views/glossary/UpdateTerm.vue | 40 +++ .../Controller/Api/CreateCGlossaryAction.php | 64 +++++ .../Controller/Api/ExportCGlossaryAction.php | 131 +++++++++ .../Api/ExportGlossaryToDocumentsAction.php | 97 +++++++ .../Api/GetGlossaryCollectionController.php | 58 ++++ .../Controller/Api/ImportCGlossaryAction.php | 135 +++++++++ .../Controller/Api/UpdateCGlossaryAction.php | 62 +++++ src/CoreBundle/Tool/Glossary.php | 2 +- src/CourseBundle/Entity/CGlossary.php | 172 +++++++++++- 19 files changed, 1547 insertions(+), 4 deletions(-) create mode 100644 assets/vue/components/glossary/GlossaryExportForm.vue create mode 100644 assets/vue/components/glossary/GlossaryForm.vue create mode 100644 assets/vue/components/glossary/GlossaryImportForm.vue create mode 100644 assets/vue/components/glossary/GlossaryLayout.vue create mode 100644 assets/vue/router/glossary.js create mode 100644 assets/vue/views/glossary/CreateTerm.vue create mode 100644 assets/vue/views/glossary/ExportGlossary.vue create mode 100644 assets/vue/views/glossary/GlossaryList.vue create mode 100644 assets/vue/views/glossary/ImportGlossary.vue create mode 100644 assets/vue/views/glossary/UpdateTerm.vue create mode 100644 src/CoreBundle/Controller/Api/CreateCGlossaryAction.php create mode 100644 src/CoreBundle/Controller/Api/ExportCGlossaryAction.php create mode 100644 src/CoreBundle/Controller/Api/ExportGlossaryToDocumentsAction.php create mode 100644 src/CoreBundle/Controller/Api/GetGlossaryCollectionController.php create mode 100644 src/CoreBundle/Controller/Api/ImportCGlossaryAction.php create mode 100644 src/CoreBundle/Controller/Api/UpdateCGlossaryAction.php diff --git a/assets/vue/components/glossary/GlossaryExportForm.vue b/assets/vue/components/glossary/GlossaryExportForm.vue new file mode 100644 index 0000000000..3d363539f4 --- /dev/null +++ b/assets/vue/components/glossary/GlossaryExportForm.vue @@ -0,0 +1,95 @@ + + + + + + diff --git a/assets/vue/components/glossary/GlossaryForm.vue b/assets/vue/components/glossary/GlossaryForm.vue new file mode 100644 index 0000000000..cda134a693 --- /dev/null +++ b/assets/vue/components/glossary/GlossaryForm.vue @@ -0,0 +1,154 @@ + + + diff --git a/assets/vue/components/glossary/GlossaryImportForm.vue b/assets/vue/components/glossary/GlossaryImportForm.vue new file mode 100644 index 0000000000..2d98680f3e --- /dev/null +++ b/assets/vue/components/glossary/GlossaryImportForm.vue @@ -0,0 +1,127 @@ + + + diff --git a/assets/vue/components/glossary/GlossaryLayout.vue b/assets/vue/components/glossary/GlossaryLayout.vue new file mode 100644 index 0000000000..5caa8ea2f6 --- /dev/null +++ b/assets/vue/components/glossary/GlossaryLayout.vue @@ -0,0 +1,9 @@ + + + diff --git a/assets/vue/router/glossary.js b/assets/vue/router/glossary.js new file mode 100644 index 0000000000..a3a1f4d8ad --- /dev/null +++ b/assets/vue/router/glossary.js @@ -0,0 +1,34 @@ +export default { + path: '/resources/glossary/:node/', + meta: { requiresAuth: true, showBreadcrumb: true }, + name: 'glossary', + component: () => import('../components/glossary/GlossaryLayout.vue'), + redirect: { name: 'GlossaryList' }, + children: [ + { + name: 'GlossaryList', + path: '', + component: () => import('../views/glossary/GlossaryList.vue') + }, + { + name: 'CreateTerm', + path: 'create', + component: () => import('../views/glossary/CreateTerm.vue') + }, + { + name: 'UpdateTerm', + path: 'edit/:id', + component: () => import('../views/glossary/UpdateTerm.vue') + }, + { + name: 'ImportGlossary', + path: '', + component: () => import('../views/glossary/ImportGlossary.vue') + }, + { + name: 'ExportGlossary', + path: '', + component: () => import('../views/glossary/ExportGlossary.vue') + }, + ] +}; diff --git a/assets/vue/router/index.js b/assets/vue/router/index.js index 2864212ed6..fa059ad1e9 100644 --- a/assets/vue/router/index.js +++ b/assets/vue/router/index.js @@ -15,6 +15,7 @@ import socialNetworkRoutes from './social'; //import courseCategoryRoutes from './coursecategory'; import documents from './documents'; import links from './links'; +import glossary from './glossary'; import store from '../store'; import MyCourseList from '../views/user/courses/List.vue'; import MySessionList from '../views/user/sessions/SessionsCurrent.vue'; @@ -123,6 +124,7 @@ const router = createRouter({ //courseCategoryRoutes, documents, links, + glossary, accountRoutes, personalFileRoutes, messageRoutes, diff --git a/assets/vue/views/glossary/CreateTerm.vue b/assets/vue/views/glossary/CreateTerm.vue new file mode 100644 index 0000000000..637d7fb6b0 --- /dev/null +++ b/assets/vue/views/glossary/CreateTerm.vue @@ -0,0 +1,35 @@ + + + diff --git a/assets/vue/views/glossary/ExportGlossary.vue b/assets/vue/views/glossary/ExportGlossary.vue new file mode 100644 index 0000000000..93666acc20 --- /dev/null +++ b/assets/vue/views/glossary/ExportGlossary.vue @@ -0,0 +1,41 @@ + + + + + diff --git a/assets/vue/views/glossary/GlossaryList.vue b/assets/vue/views/glossary/GlossaryList.vue new file mode 100644 index 0000000000..66df121312 --- /dev/null +++ b/assets/vue/views/glossary/GlossaryList.vue @@ -0,0 +1,258 @@ + + + diff --git a/assets/vue/views/glossary/ImportGlossary.vue b/assets/vue/views/glossary/ImportGlossary.vue new file mode 100644 index 0000000000..3279c30954 --- /dev/null +++ b/assets/vue/views/glossary/ImportGlossary.vue @@ -0,0 +1,35 @@ + + + diff --git a/assets/vue/views/glossary/UpdateTerm.vue b/assets/vue/views/glossary/UpdateTerm.vue new file mode 100644 index 0000000000..3f42497e76 --- /dev/null +++ b/assets/vue/views/glossary/UpdateTerm.vue @@ -0,0 +1,40 @@ + + + diff --git a/src/CoreBundle/Controller/Api/CreateCGlossaryAction.php b/src/CoreBundle/Controller/Api/CreateCGlossaryAction.php new file mode 100644 index 0000000000..b1e078b0a4 --- /dev/null +++ b/src/CoreBundle/Controller/Api/CreateCGlossaryAction.php @@ -0,0 +1,64 @@ +getContent(), true); + $title = $data['name']; + $description = $data['description']; + $parentResourceNodeId = $data['parentResourceNodeId']; + $resourceLinkList = json_decode($data['resourceLinkList'], true); + $sid = (int) $data['sid']; + $cid = (int) $data['cid']; + + $course = null; + $session = null; + if (0 !== $cid) { + $course = $em->getRepository(Course::class)->find($cid); + } + if (0 !== $sid) { + $session = $em->getRepository(Session::class)->find($sid); + } + + // Check if the term already exists + $qb = $repo->getResourcesByCourse($course, $session) + ->andWhere('resource.name = :name') + ->setParameter('name', $title); + $existingGlossaryTerm = $qb->getQuery()->getOneOrNullResult(); + if ($existingGlossaryTerm !== null) { + throw new BadRequestHttpException('The glossary term already exists.'); + } + + $glossary = (new CGlossary()) + ->setName($title) + ->setDescription($description) + ; + + + if (!empty($parentResourceNodeId)) { + $glossary->setParentResourceNode($parentResourceNodeId); + } + + if (!empty($resourceLinkList)) { + $glossary->setResourceLinkArray($resourceLinkList); + } + + return $glossary; + } +} diff --git a/src/CoreBundle/Controller/Api/ExportCGlossaryAction.php b/src/CoreBundle/Controller/Api/ExportCGlossaryAction.php new file mode 100644 index 0000000000..25606c533f --- /dev/null +++ b/src/CoreBundle/Controller/Api/ExportCGlossaryAction.php @@ -0,0 +1,131 @@ +get('format'); + $cid = $request->request->get('cid'); + $sid = $request->request->get('sid'); + + if (!in_array($format, ['csv', 'xls', 'pdf'])) { + throw new BadRequestHttpException('Invalid export format'); + } + + $exportPath = $kernel->getCacheDir(); + $course = null; + $session = null; + if (0 !== $cid) { + $course = $em->getRepository(Course::class)->find($cid); + } + if (0 !== $sid) { + $session = $em->getRepository(Session::class)->find($sid); + } + + $qb = $repo->getResourcesByCourse($course, $session); + $glossaryItems = $qb->getQuery()->getResult(); + + $exportFilePath = $this->generateExportFile($glossaryItems, $format, $exportPath, $translator); + + $file = new File($exportFilePath); + $response = new Response($file->getContent()); + $response->headers->set('Content-Type', $file->getMimeType()); + $response->headers->set('Content-Disposition', 'attachment; filename="glossary.'.$format.'"'); + + unlink($exportFilePath); + + return $response; + } + + private function generateExportFile(array $glossaryItems, string $format, string $exportPath, TranslatorInterface $translator): string + { + switch ($format) { + case 'csv': + return $this->generateCsvFile($glossaryItems, $exportPath); + case 'xls': + return $this->generateExcelFile($glossaryItems, $exportPath); + case 'pdf': + return $this->generatePdfFile($glossaryItems, $exportPath, $translator); + default: + throw new NotSupported('Export format not supported'); + } + } + + private function generateCsvFile(array $glossaryItems, string $exportPath): string + { + $csvFilePath = $exportPath.'/glossary.csv'; + $csvContent = ''; + /* @var CGlossary $item */ + foreach ($glossaryItems as $item) { + $csvContent .= $item->getName() . ',' . $item->getDescription() . "\n"; + } + file_put_contents($csvFilePath, $csvContent); + + return $csvFilePath; + } + + private function generateExcelFile(array $glossaryItems, string $exportPath): string + { + $excelFilePath = $exportPath.'/glossary.xlsx'; + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + /* @var CGlossary $item */ + foreach ($glossaryItems as $index => $item) { + $row = $index + 1; + $sheet->setCellValue('A' . $row, $item->getName()); + $sheet->setCellValue('B' . $row, $item->getDescription()); + } + + $writer = IOFactory::createWriter($spreadsheet, 'Xlsx'); + $writer->save($excelFilePath); + + return $excelFilePath; + } + + private function generatePdfFile(array $glossaryItems, string $exportPath, TranslatorInterface $translator): string + { + $pdfFilePath = $exportPath . '/glossary.pdf'; + + $mpdf = new Mpdf(); + + $html = '

'.$translator->trans('Glossary').'

'; + $html .= ''; + $html .= ''; + /* @var CGlossary $item */ + foreach ($glossaryItems as $item) { + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + } + $html .= '
'.$translator->trans('Term').''.$translator->trans('Definition').'
' . $item->getName(). '' . $item->getDescription() . '
'; + + $mpdf->WriteHTML($html); + + $mpdf->Output($pdfFilePath, 'F'); + + return $pdfFilePath; + } +} diff --git a/src/CoreBundle/Controller/Api/ExportGlossaryToDocumentsAction.php b/src/CoreBundle/Controller/Api/ExportGlossaryToDocumentsAction.php new file mode 100644 index 0000000000..34c8cc4d40 --- /dev/null +++ b/src/CoreBundle/Controller/Api/ExportGlossaryToDocumentsAction.php @@ -0,0 +1,97 @@ +getContent(), true); + $parentResourceNodeId = $data['parentResourceNodeId']; + $resourceLinkList = json_decode($data['resourceLinkList'], true); + + $exportPath = $kernel->getCacheDir(); + $glossaryItems = $repo->findAll(); + + $pdfFilePath = $this->generatePdfFile($glossaryItems, $exportPath, $translator); + + if ($pdfFilePath) { + $fileName = basename($pdfFilePath); + $uploadFile = new UploadedFile( + $pdfFilePath, + $fileName + ); + + $document = new CDocument(); + $document->setTitle($fileName); + $document->setUploadFile($uploadFile); + $document->setFiletype('file'); + + if (!empty($parentResourceNodeId)) { + $document->setParentResourceNode($parentResourceNodeId); + } + + if (!empty($resourceLinkList)) { + $document->setResourceLinkArray($resourceLinkList); + } + + // Save the CDocument entity to the database + $em->persist($document); + $em->flush(); + + unlink($pdfFilePath); + } + + return $pdfFilePath; + } + + private function generatePdfFile(array $glossaryItems, string $exportPath, TranslatorInterface $translator): string + { + + $date = date('Y-m-d'); + $pdfFileName = 'glossary_' . $date . '.pdf'; + $pdfFilePath = $exportPath . '/' . $pdfFileName; + + $mpdf = new Mpdf(); + + $html = '

'.$translator->trans('Glossary').'

'; + $html .= ''; + $html .= ''; + /* @var CGlossary $item */ + foreach ($glossaryItems as $item) { + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + } + $html .= '
'.$translator->trans('Term').''.$translator->trans('Definition').'
' . $item->getName(). '' . $item->getDescription() . '
'; + + $mpdf->WriteHTML($html); + + $mpdf->Output($pdfFilePath, 'F'); + + return $pdfFilePath; + } +} diff --git a/src/CoreBundle/Controller/Api/GetGlossaryCollectionController.php b/src/CoreBundle/Controller/Api/GetGlossaryCollectionController.php new file mode 100644 index 0000000000..69af214a39 --- /dev/null +++ b/src/CoreBundle/Controller/Api/GetGlossaryCollectionController.php @@ -0,0 +1,58 @@ +query->getInt('cid'); + $sid = $request->query->getInt('sid'); + $q = $request->query->get('q'); + $course = null; + $session = null; + if ($cid) { + $course = $em->getRepository(Course::class)->find($cid); + } + + if ($sid) { + $session = $em->getRepository(Session::class)->find($sid); + } + + $qb = $repo->getResourcesByCourse($course, $session); + if ($q) { + $qb->andWhere($qb->expr()->like('resource.name', ':name')) + ->setParameter('name', '%' . $q . '%'); + } + $glossaries = $qb->getQuery()->getResult(); + + $dataResponse = []; + if ($glossaries) { + /* @var CGlossary $item */ + foreach ($glossaries as $item) { + $dataResponse[] = + [ + 'iid' => $item->getIid(), + 'id' => $item->getIid(), + 'name' => $item->getName(), + 'description' => $item->getDescription(), + ]; + } + } + + return new JsonResponse($dataResponse); + } +} diff --git a/src/CoreBundle/Controller/Api/ImportCGlossaryAction.php b/src/CoreBundle/Controller/Api/ImportCGlossaryAction.php new file mode 100644 index 0000000000..5a69b593f1 --- /dev/null +++ b/src/CoreBundle/Controller/Api/ImportCGlossaryAction.php @@ -0,0 +1,135 @@ +files->get('file'); + $fileType = $request->request->get('file_type'); + $replace = $request->request->get('replace'); + $update = $request->request->get('update'); + $cid = $request->request->get('cid'); + $sid = $request->request->get('sid'); + + $course = null; + $session = null; + if (0 !== $cid) { + $course = $em->getRepository(Course::class)->find($cid); + } + if (0 !== $sid) { + $session = $em->getRepository(Session::class)->find($sid); + } + + if (!$file instanceof UploadedFile || !$file->isValid()) { + throw new BadRequestHttpException('Invalid file'); + } + + $data = []; + if ($fileType === 'csv') { + if (($handle = fopen($file->getPathname(), 'r')) !== false) { + $header = fgetcsv($handle, 0, ';'); + while (($row = fgetcsv($handle, 0, ';')) !== false) { + $term = isset($row[0]) ? trim($row[0]) : ''; + $definition = isset($row[1]) ? trim($row[1]) : ''; + $data[$term] = $definition; + } + fclose($handle); + } + } elseif ($fileType === 'xls') { + $spreadsheet = IOFactory::load($file->getPathname()); + $sheet = $spreadsheet->getActiveSheet(); + $firstRow = true; + foreach ($sheet->getRowIterator() as $row) { + if ($firstRow) { + $firstRow = false; + continue; + } + $cellIterator = $row->getCellIterator(); + $cellIterator->setIterateOnlyExistingCells(false); + $rowData = []; + foreach ($cellIterator as $cell) { + $rowData[] = $cell->getValue(); + } + $term = isset($rowData[0]) ? utf8_decode(trim($rowData[0])) : ''; + $definition = isset($rowData[1]) ? utf8_decode(trim($rowData[1])) : ''; + $data[$term] = $definition; + } + } else { + throw new BadRequestHttpException('Invalid file type'); + } + + if (empty($data)) { + throw new BadRequestHttpException('Invalid data'); + } + + if ('true' === $replace) { + $qb = $repo->getResourcesByCourse($course, $session); + $allGlossaries = $qb->getQuery()->getResult(); + if ($allGlossaries) { + /* @var CGlossary $item */ + foreach ($allGlossaries as $item) { + $termToDelete = $repo->find($item->getIid()); + if (null !== $termToDelete) { + $repo->delete($termToDelete); + } + } + } + + } + + if ('true' === $update) { + foreach ($data as $termToUpdate => $descriptionToUpdate) { + // Check if the term already exists + $qb = $repo->getResourcesByCourse($course, $session) + ->andWhere('resource.name = :name') + ->setParameter('name', $termToUpdate); + /* @var CGlossary $existingGlossaryTerm */ + $existingGlossaryTerm = $qb->getQuery()->getOneOrNullResult(); + if ($existingGlossaryTerm !== null) { + $existingGlossaryTerm->setDescription($descriptionToUpdate); + $repo->update($existingGlossaryTerm); + unset($data[$termToUpdate]); + } + } + } + + foreach ($data as $term => $description) { + $qb = $repo->getResourcesByCourse($course, $session) + ->andWhere('resource.name = :name') + ->setParameter('name', $term); + /* @var CGlossary $existingNewGlossaryTerm */ + $existingNewGlossaryTerm = $qb->getQuery()->getOneOrNullResult(); + if (!$existingNewGlossaryTerm) { + $newGlossary = (new CGlossary()) + ->setName($term) + ->setDescription($description) + ->setParent($course) + ->addCourseLink($course, $session) + ; + $repo->create($newGlossary); + } + } + + $response = new Response(json_encode($data), Response::HTTP_OK, ['Content-Type' => 'application/json']); + + return $response; + } + +} diff --git a/src/CoreBundle/Controller/Api/UpdateCGlossaryAction.php b/src/CoreBundle/Controller/Api/UpdateCGlossaryAction.php new file mode 100644 index 0000000000..5978c5d80b --- /dev/null +++ b/src/CoreBundle/Controller/Api/UpdateCGlossaryAction.php @@ -0,0 +1,62 @@ +getContent(), true); + $title = $data['name']; + $description = $data['description']; + $parentResourceNodeId = $data['parentResourceNodeId']; + $resourceLinkList = json_decode($data['resourceLinkList'], true); + $sid = (int) $data['sid']; + $cid = (int) $data['cid']; + + $course = null; + $session = null; + if (0 !== $cid) { + $course = $em->getRepository(Course::class)->find($cid); + } + if (0 !== $sid) { + $session = $em->getRepository(Session::class)->find($sid); + } + + // Check if the term already exists + $qb = $repo->getResourcesByCourse($course, $session) + ->andWhere('resource.name = :name') + ->setParameter('name', $title); + $existingGlossaryTerm = $qb->getQuery()->getOneOrNullResult(); + if ($existingGlossaryTerm !== null && $existingGlossaryTerm->getIid() !== $glossary->getIid()) { + throw new BadRequestHttpException('The glossary term already exists.'); + } + + $glossary->setName($title); + $glossary->setDescription($description); + + if (!empty($parentResourceNodeId)) { + $glossary->setParentResourceNode($parentResourceNodeId); + } + + if (!empty($resourceLinkList)) { + $glossary->setResourceLinkArray($resourceLinkList); + } + + return $glossary; + } +} diff --git a/src/CoreBundle/Tool/Glossary.php b/src/CoreBundle/Tool/Glossary.php index 2c547f60a3..84c78edf62 100644 --- a/src/CoreBundle/Tool/Glossary.php +++ b/src/CoreBundle/Tool/Glossary.php @@ -17,7 +17,7 @@ class Glossary extends AbstractTool implements ToolInterface public function getLink(): string { - return '/main/glossary/index.php'; + return '/resources/glossary/:nodeId/'; } public function getIcon(): string diff --git a/src/CourseBundle/Entity/CGlossary.php b/src/CourseBundle/Entity/CGlossary.php index 68634eadd8..e5f80a6b70 100644 --- a/src/CourseBundle/Entity/CGlossary.php +++ b/src/CourseBundle/Entity/CGlossary.php @@ -6,31 +6,197 @@ declare(strict_types=1); namespace Chamilo\CourseBundle\Entity; +use ApiPlatform\Doctrine\Orm\Filter\OrderFilter; +use ApiPlatform\Doctrine\Orm\Filter\SearchFilter; +use ApiPlatform\Metadata\ApiFilter; +use ApiPlatform\Metadata\ApiProperty; +use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\Delete; +use ApiPlatform\Metadata\Get; +use ApiPlatform\Metadata\GetCollection; +use ApiPlatform\Metadata\Post; +use ApiPlatform\Metadata\Put; +use Chamilo\CoreBundle\Controller\Api\CreateCGlossaryAction; +use Chamilo\CoreBundle\Controller\Api\ExportCGlossaryAction; +use Chamilo\CoreBundle\Controller\Api\ExportGlossaryToDocumentsAction; +use Chamilo\CoreBundle\Controller\Api\GetGlossaryCollectionController; +use Chamilo\CoreBundle\Controller\Api\ImportCGlossaryAction; +use Chamilo\CoreBundle\Controller\Api\UpdateCGlossaryAction; use Chamilo\CoreBundle\Entity\AbstractResource; use Chamilo\CoreBundle\Entity\ResourceInterface; +use Chamilo\CourseBundle\Repository\CGlossaryRepository; use Doctrine\ORM\Mapping as ORM; use Stringable; +use Symfony\Component\Serializer\Annotation\Groups; use Symfony\Component\Validator\Constraints as Assert; /** * Course glossary. */ +#[ApiResource( + shortName: 'Glossary', + operations: [ + new Put( + controller: UpdateCGlossaryAction::class, + security: "is_granted('EDIT', object.resourceNode)", + validationContext: [ + 'groups' => ['media_object_create', 'glossary:write'] + ], + deserialize: false + ), + new Get(security: "is_granted('VIEW', object.resourceNode)"), + new Delete(security: "is_granted('DELETE', object.resourceNode)"), + new Post( + controller: CreateCGlossaryAction::class, + openapiContext: [ + 'requestBody' => [ + 'content' => [ + 'application/json' => [ + 'schema' => [ + 'type' => 'object', + 'properties' => [ + 'name' => ['type' => 'string'], + 'description' => ['type' => 'string'], + 'parentResourceNodeId' => ['type' => 'integer'], + 'resourceLinkList' => [ + 'type' => 'array', + 'items' => [ + 'type' => 'object', + 'properties' => [ + 'visibility' => ['type' => 'integer'], + 'cid' => ['type' => 'integer'], + 'gid' => ['type' => 'integer'], + 'sid' => ['type' => 'integer'] + ] + ] + ] + ], + 'required' => ['name'], + ], + ], + ], + ], + ], + security: "is_granted('ROLE_CURRENT_COURSE_TEACHER') or is_granted('ROLE_CURRENT_COURSE_SESSION_TEACHER')", + validationContext: ['groups' => ['Default', 'media_object_create', 'glossary:write']], + deserialize: false + ), + new GetCollection( + controller: GetGlossaryCollectionController::class, + openapiContext: [ + 'parameters' => [ + [ + 'name' => 'resourceNode.parent', + 'in' => 'query', + 'required' => true, + 'description' => 'Resource node Parent', + 'schema' => ['type' => 'integer'] + ], + [ + 'name' => 'cid', + 'in' => 'query', + 'required' => true, + 'description' => 'Course id', + 'schema' => [ + 'type' => 'integer' + ] + ], + [ + 'name' => 'sid', + 'in' => 'query', + 'required' => false, + 'description' => 'Session id', + 'schema' => [ + 'type' => 'integer' + ] + ], + [ + 'name' => 'q', + 'in' => 'query', + 'required' => false, + 'description' => 'Search term', + 'schema' => [ + 'type' => 'string' + ] + ] + ] + ] + ), + new Post( + uriTemplate: '/glossaries/import', + controller: ImportCGlossaryAction::class, + openapiContext: [ + 'summary' => 'Import a glossary', + 'requestBody' => [ + 'content' => [ + 'multipart/form-data' => [ + 'schema' => [ + 'type' => 'object', + 'properties' => [ + 'file' => [ + 'type' => 'string', + 'format' => 'binary', + ], + ], + ], + ], + ], + ], + 'responses' => [ + '200' => [ + 'description' => 'Glossaries imported successfully', + ], + ], + ], + security: "is_granted('ROLE_CURRENT_COURSE_TEACHER') or is_granted('ROLE_CURRENT_COURSE_SESSION_TEACHER')", + validationContext: ['groups' => ['Default', 'media_object_create', 'glossary:write']], + deserialize: false + ), + new Post( + uriTemplate: '/glossaries/export', + controller: ExportCGlossaryAction::class, + security: "is_granted('ROLE_CURRENT_COURSE_TEACHER') or is_granted('ROLE_CURRENT_COURSE_SESSION_TEACHER')", + validationContext: ['groups' => ['Default', 'media_object_create', 'glossary:write']], + deserialize: false + ), + new Post( + uriTemplate: '/glossaries/export_to_documents', + controller: ExportGlossaryToDocumentsAction::class, + security: "is_granted('ROLE_CURRENT_COURSE_TEACHER') or is_granted('ROLE_CURRENT_COURSE_SESSION_TEACHER')", + validationContext: ['groups' => ['Default', 'media_object_create', 'glossary:write']], + deserialize: false + ), + ], + normalizationContext: [ + 'groups' => ['glossary:read', 'resource_node:read'], + ], + denormalizationContext: [ + 'groups' => ['glossary:write'], + ], +)] +#[ApiFilter(SearchFilter::class, properties: ['name' => 'partial'])] +#[ApiFilter(OrderFilter::class, properties: ['iid', 'name', 'createdAt', 'updatedAt'])] #[ORM\Table(name: 'c_glossary')] -#[ORM\Entity(repositoryClass: \Chamilo\CourseBundle\Repository\CGlossaryRepository::class)] +#[ORM\Entity(repositoryClass: CGlossaryRepository::class)] class CGlossary extends AbstractResource implements ResourceInterface, Stringable { + #[ApiProperty(identifier: true)] + #[Groups(['glossary:read'])] #[ORM\Column(name: 'iid', type: 'integer')] #[ORM\Id] #[ORM\GeneratedValue] protected int $iid; + #[Groups(['glossary:read', 'glossary:write'])] #[Assert\NotBlank] #[ORM\Column(name: 'name', type: 'text', nullable: false)] protected string $name; + #[Groups(['glossary:read', 'glossary:write'])] #[ORM\Column(name: 'description', type: 'text', nullable: false)] protected ?string $description = null; + #[Groups(['glossary:read', 'glossary:write'])] #[ORM\Column(name: 'display_order', type: 'integer', nullable: true)] protected ?int $displayOrder = null; @@ -63,7 +229,7 @@ class CGlossary extends AbstractResource implements ResourceInterface, Stringabl * * @return string */ - public function getDescription() + public function getDescription(): ?string { return $this->description; } @@ -80,7 +246,7 @@ class CGlossary extends AbstractResource implements ResourceInterface, Stringabl * * @return int */ - public function getDisplayOrder() + public function getDisplayOrder(): ?int { return $this->displayOrder; }