diff --git a/assets/vue/components/basecomponents/ChamiloIcons.js b/assets/vue/components/basecomponents/ChamiloIcons.js
index abcaed0c0e..c367a45e46 100644
--- a/assets/vue/components/basecomponents/ChamiloIcons.js
+++ b/assets/vue/components/basecomponents/ChamiloIcons.js
@@ -111,4 +111,6 @@ export const chamiloIconToClass = {
"ticket": "mdi mdi-ticket-account",
"certificate-selected": "mdi mdi-star",
"certificate-not-selected": "mdi mdi-star-outline",
+ "template-selected": "mdi mdi-file-check",
+ "template-not-selected": "mdi mdi-file-outline",
};
diff --git a/assets/vue/composables/fileUtils.js b/assets/vue/composables/fileUtils.js
index 4128e7226d..3d2a8f583c 100644
--- a/assets/vue/composables/fileUtils.js
+++ b/assets/vue/composables/fileUtils.js
@@ -16,6 +16,12 @@ export function useFileUtils() {
return isFile(fileData) && isAudio
}
+ const isHtml = (fileData) => {
+ const mimeType = fileData.resourceNode.resourceFile.mimeType
+ const isHtml = mimeType.split("/")[1].toLowerCase() === "html"
+ return isFile(fileData) && isHtml
+ }
+
const isFile = (fileData) => {
return fileData.resourceNode && fileData.resourceNode.resourceFile
}
@@ -25,5 +31,6 @@ export function useFileUtils() {
isImage,
isVideo,
isAudio,
+ isHtml,
}
}
diff --git a/assets/vue/views/documents/CreateFile.vue b/assets/vue/views/documents/CreateFile.vue
index 08394920a1..32bcd185d5 100644
--- a/assets/vue/views/documents/CreateFile.vue
+++ b/assets/vue/views/documents/CreateFile.vue
@@ -97,13 +97,14 @@ export default {
this.item.contentFile = templateContent;
},
fetchTemplates() {
- axios.get('/system-templates')
+ const courseId = this.$route.query.cid;
+ axios.get(`/template/all-templates/${courseId}`)
.then(response => {
- console.log(response.data);
this.templates = response.data;
+ console.log('Templates fetched successfully:', this.templates);
})
.catch(error => {
- console.error('There was an error fetching the templates:', error);
+ console.error('Error fetching templates:', error);
});
},
getCertificateTags(){
diff --git a/assets/vue/views/documents/DocumentsList.vue b/assets/vue/views/documents/DocumentsList.vue
index 28bfba9a1a..4739660823 100644
--- a/assets/vue/views/documents/DocumentsList.vue
+++ b/assets/vue/views/documents/DocumentsList.vue
@@ -191,6 +191,13 @@
type="black"
@click="selectAsDefaultCertificate(slotProps.data)"
/>
+
@@ -305,6 +312,41 @@
@document-not-saved="recordedAudioNotSaved"
/>
+
+
+
diff --git a/assets/vue/views/documents/UpdateFile.vue b/assets/vue/views/documents/UpdateFile.vue
index 9d1ce4c2f0..8feb72d891 100644
--- a/assets/vue/views/documents/UpdateFile.vue
+++ b/assets/vue/views/documents/UpdateFile.vue
@@ -87,9 +87,11 @@ export default {
this.$router.back();
},
fetchTemplates() {
- axios.get('/system-templates')
+ const cid = this.$route.query.cid;
+ axios.get(`/template/all-templates/${cid}`)
.then(response => {
this.templates = response.data;
+ console.log('Templates fetched successfully:', this.templates);
})
.catch(error => {
console.error('Error fetching the templates:', error);
diff --git a/public/main/admin/settings.lib.php b/public/main/admin/settings.lib.php
index bd0331503d..abdde4942a 100644
--- a/public/main/admin/settings.lib.php
+++ b/public/main/admin/settings.lib.php
@@ -1067,30 +1067,17 @@ function actionsFilter($id)
return $return;
}
-/**
- * Display the image of the template in the sortable table.
- *
- * @param string $image the image
- *
- * @return string code for the image
- *
- * @author Patrick Cool , Ghent University, Belgium
- *
- * @version August 2008
- *
- * @since v1.8.6
- */
-function searchImageFilter($id)
+function searchImageFilter(int $id): string
{
$em = Database::getManager();
/** @var SystemTemplate $template */
$template = $em->find(SystemTemplate::class, $id);
- $assetRepo = Container::getAssetRepository();
- $imageUrl = $assetRepo->getAssetUrl($template->getImage());
+ if (null !== $template->getImage()) {
+ $assetRepo = Container::getAssetRepository();
+ $imageUrl = $assetRepo->getAssetUrl($template->getImage());
- if (!empty($imageUrl)) {
return '
';
} else {
return '
';
diff --git a/src/CoreBundle/Controller/TemplateController.php b/src/CoreBundle/Controller/TemplateController.php
index 8a69322a7d..05fcec36ab 100644
--- a/src/CoreBundle/Controller/TemplateController.php
+++ b/src/CoreBundle/Controller/TemplateController.php
@@ -5,29 +5,169 @@ declare(strict_types=1);
namespace Chamilo\CoreBundle\Controller;
+use Chamilo\CoreBundle\Entity\Asset;
+use Chamilo\CoreBundle\Entity\Course;
+use Chamilo\CoreBundle\Entity\Templates;
+use Chamilo\CoreBundle\Entity\User;
use Chamilo\CoreBundle\Repository\AssetRepository;
+use Chamilo\CoreBundle\Repository\Node\CourseRepository;
use Chamilo\CoreBundle\Repository\SystemTemplateRepository;
+use Chamilo\CoreBundle\Repository\TemplatesRepository;
+use Chamilo\CourseBundle\Entity\CDocument;
+use Chamilo\CourseBundle\Repository\CDocumentRepository;
+use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Routing\Annotation\Route;
+#[Route('/template')]
class TemplateController extends AbstractController
{
- #[Route('/system-templates', name: 'system-templates')]
- public function getTemplates(SystemTemplateRepository $templateRepository, AssetRepository $assetRepository): JsonResponse
+ #[Route('/document-templates/create', methods: ['POST'])]
+ public function createDocumentTemplate(Request $request, EntityManagerInterface $entityManager, AssetRepository $assetRepo): Response
{
- $templates = $templateRepository->findAll();
+ $documentId = (int) $request->request->get('refDoc');
+ $title = $request->request->get('title');
+ $cid = $request->request->get('cid');
+ $imageFile = $request->files->get('thumbnail');
+
+ if (!$imageFile) {
+ return $this->json(['error' => 'No image provided.'], Response::HTTP_BAD_REQUEST);
+ }
+
+ /** @var User $user */
+ $user = $this->getUser();
+ $course = null;
+ if ($cid) {
+ $course = $entityManager->getRepository(Course::class)->find($cid);
+ }
+
+ $asset = new Asset();
+ $asset->setCategory(Asset::TEMPLATE);
+ $asset->setFile($imageFile);
+ $asset->setTitle($imageFile->getClientOriginalName());
+ $entityManager->persist($asset);
+ $entityManager->flush();
+
+ $template = new Templates();
+ $template->setTitle($title);
+ $template->setDescription('');
+ $template->setRefDoc($documentId);
+ $template->setCourse($course);
+ $template->setUser($user);
+ $template->setImage($asset);
+ $entityManager->persist($template);
+
+ $document = $entityManager->getRepository(CDocument::class)->find($documentId);
+ if ($document) {
+ $document->setTemplate(true);
+ $entityManager->persist($document);
+ } else {
+ return $this->json(['error' => 'Document not found.'], Response::HTTP_NOT_FOUND);
+ }
+
+ $entityManager->flush();
+
+ return $this->json(['message' => 'Template created successfully.']);
+ }
+
+ #[Route('/document-templates/{documentId}/is-template', methods: ['GET'])]
+ public function isDocumentTemplate(int $documentId, EntityManagerInterface $entityManager): Response
+ {
+ $template = $entityManager->getRepository(Templates::class)->findOneBy(['refDoc' => $documentId]);
+
+ return $this->json([
+ 'isTemplate' => null !== $template,
+ ]);
+ }
+
+ #[Route('/document-templates/{documentId}/delete', methods: ['POST'])]
+ public function deleteDocumentTemplate(int $documentId, EntityManagerInterface $entityManager): Response
+ {
+ $template = $entityManager->getRepository(Templates::class)->findOneBy(['refDoc' => $documentId]);
+
+ if (!$template) {
+ return $this->json(['error' => 'Template not found.'], Response::HTTP_NOT_FOUND);
+ }
+
+ $entityManager->remove($template);
+
+ $document = $entityManager->getRepository(CDocument::class)->find($documentId);
+ if ($document) {
+ $document->setTemplate(false);
+ $entityManager->persist($document);
+ } else {
+ return $this->json(['error' => 'Document not found.'], Response::HTTP_NOT_FOUND);
+ }
+
+ $entityManager->flush();
+
+ return $this->json(['message' => 'Template deleted successfully']);
+ }
+
+ #[Route('/all-templates/{courseId}', name: 'all-templates')]
+ public function getAllTemplates($courseId, SystemTemplateRepository $systemTemplateRepository, TemplatesRepository $templatesRepository, CourseRepository $courseRepository, AssetRepository $assetRepository, CDocumentRepository $documentRepository): JsonResponse
+ {
+ $course = $courseRepository->find($courseId);
+ if (!$course) {
+ throw new NotFoundHttpException("Course not found");
+ }
+
+ $systemTemplates = $systemTemplateRepository->findAll();
+ $platformTemplates = $this->formatSystemTemplates($systemTemplates, $assetRepository);
+
+ $courseDocumentTemplates = $this->formatCourseDocumentTemplates($course, $templatesRepository, $assetRepository, $documentRepository);
+
+ $allTemplates = array_merge($platformTemplates, $courseDocumentTemplates);
+
+ return $this->json($allTemplates);
+ }
+
+
+ private function formatSystemTemplates(array $systemTemplates, AssetRepository $assetRepository): array
+ {
+ return array_map(function ($template) use ($assetRepository) {
+ $imageUrl = null;
+ if ($template->hasImage()) {
+ $imageUrl = $assetRepository->getAssetUrl($template->getImage());
+ }
- $data = array_map(function ($template) use ($assetRepository) {
return [
'id' => $template->getId(),
'title' => $template->getTitle(),
'comment' => $template->getComment(),
'content' => $template->getContent(),
- 'image' => $template->getImage() ? $assetRepository->getAssetUrl($template->getImage()) : null,
+ 'image' => $imageUrl,
];
- }, $templates);
+ }, $systemTemplates);
+ }
+
+ private function formatCourseDocumentTemplates(Course $course, TemplatesRepository $templatesRepository, AssetRepository $assetRepository, CDocumentRepository $documentRepository): array
+ {
+ $courseTemplates = $templatesRepository->findCourseDocumentTemplates($course);
+
+ return array_map(function ($template) use ($assetRepository, $documentRepository) {
+ $imageUrl = null;
+ if ($template->hasImage()) {
+ $imageUrl = $assetRepository->getAssetUrl($template->getImage());
+ }
+
+ $document = $documentRepository->find($template->getRefDoc());
+ $content = '';
+ if (null !== $document && null !== $document->getResourceNode() && null !== $document->getResourceNode()->getResourceFile()) {
+ $content = $documentRepository->getResourceFileContent($document);
+ }
- return $this->json($data);
+ return [
+ 'id' => $template->getId(),
+ 'title' => $template->getTitle(),
+ 'comment' => $template->getDescription(),
+ 'content' => $content,
+ 'image' => $imageUrl,
+ ];
+ }, $courseTemplates);
}
}
diff --git a/src/CoreBundle/Entity/Asset.php b/src/CoreBundle/Entity/Asset.php
index 9f2770e5b4..c8d4d5fb33 100644
--- a/src/CoreBundle/Entity/Asset.php
+++ b/src/CoreBundle/Entity/Asset.php
@@ -37,6 +37,7 @@ class Asset implements Stringable
public const EXERCISE_ATTEMPT = 'exercise_attempt';
public const EXERCISE_FEEDBACK = 'exercise_feedback';
public const SYSTEM_TEMPLATE = 'system_template';
+ public const TEMPLATE = 'template';
public const SESSION = 'session';
#[ORM\Id]
diff --git a/src/CoreBundle/Entity/Templates.php b/src/CoreBundle/Entity/Templates.php
index c9b044c3dd..b14171ba71 100644
--- a/src/CoreBundle/Entity/Templates.php
+++ b/src/CoreBundle/Entity/Templates.php
@@ -41,8 +41,9 @@ class Templates
#[ORM\Column(name: 'ref_doc', type: 'integer', nullable: false)]
protected int $refDoc;
- #[ORM\Column(name: 'image', type: 'string', length: 250, nullable: false)]
- protected string $image;
+ #[ORM\ManyToOne(targetEntity: Asset::class, cascade: ['persist'])]
+ #[ORM\JoinColumn(name: 'image_id', referencedColumnName: 'id', onDelete: 'SET NULL')]
+ protected ?Asset $image = null;
/**
* Set title.
@@ -110,26 +111,21 @@ class Templates
return $this->refDoc;
}
- /**
- * Set image.
- *
- * @return Templates
- */
- public function setImage(string $image)
+ public function getImage(): ?Asset
+ {
+ return $this->image;
+ }
+
+ public function setImage(?Asset $image): self
{
$this->image = $image;
return $this;
}
- /**
- * Get image.
- *
- * @return string
- */
- public function getImage()
+ public function hasImage(): bool
{
- return $this->image;
+ return null !== $this->image;
}
/**
diff --git a/src/CoreBundle/Migrations/Schema/V200/Version20240129225700.php b/src/CoreBundle/Migrations/Schema/V200/Version20240129225700.php
new file mode 100644
index 0000000000..59619908d8
--- /dev/null
+++ b/src/CoreBundle/Migrations/Schema/V200/Version20240129225700.php
@@ -0,0 +1,32 @@
+addSql('ALTER TABLE templates ADD image_id BINARY(16) DEFAULT NULL COMMENT \'(DC2Type:uuid)\'');
+ $this->addSql('ALTER TABLE templates ADD CONSTRAINT FK_6F287D8E3DA5256D FOREIGN KEY (image_id) REFERENCES asset (id) ON DELETE SET NULL');
+ $this->addSql('CREATE INDEX IDX_6F287D8E3DA5256D ON templates (image_id)');
+ }
+
+ public function down(Schema $schema) : void
+ {
+ // this down() migration is auto-generated, please modify it to your needs
+ $this->addSql('ALTER TABLE templates DROP FOREIGN KEY FK_6F287D8E3DA5256D');
+ $this->addSql('DROP INDEX IDX_6F287D8E3DA5256D ON templates');
+ $this->addSql('ALTER TABLE templates DROP image_id');
+ }
+}
diff --git a/src/CoreBundle/Migrations/Schema/V200/Version20240130161800.php b/src/CoreBundle/Migrations/Schema/V200/Version20240130161800.php
new file mode 100644
index 0000000000..577458c4ae
--- /dev/null
+++ b/src/CoreBundle/Migrations/Schema/V200/Version20240130161800.php
@@ -0,0 +1,76 @@
+getContainer();
+
+ /** @var Kernel $kernel */
+ $kernel = $container->get('kernel');
+ $rootPath = $kernel->getProjectDir();
+ $doctrine = $container->get('doctrine');
+
+ $em = $doctrine->getManager();
+ $connection = $em->getConnection();
+
+ $sql = "SELECT id, image, c_id FROM templates WHERE image IS NOT NULL";
+ $stmt = $connection->prepare($sql);
+ $result = $stmt->executeQuery();
+
+ while ($row = $result->fetchAssociative()) {
+ $imagePath = $row['image'];
+ $templateId = $row['id'];
+ $courseId = $row['c_id'];
+
+ $courseDirectorySql = "SELECT directory FROM course WHERE id = :courseId";
+ $courseStmt = $connection->prepare($courseDirectorySql);
+ $courseResult = $courseStmt->executeQuery(['courseId' => $courseId]);
+
+ $courseRow = $courseResult->fetchAssociative();
+
+ if ($courseRow) {
+ $directory = $courseRow['directory'];
+ $thumbPath = $rootPath.'/app/courses/'.$directory.'/upload/template_thumbnails/'.$imagePath;
+
+ if (file_exists($thumbPath)) {
+ $mimeType = mime_content_type($thumbPath);
+ $fileName = basename($thumbPath);
+ $file = new UploadedFile($thumbPath, $fileName, $mimeType, null, true);
+
+ $asset = new Asset();
+ $asset->setCategory(Asset::TEMPLATE);
+ $asset->setTitle($fileName);
+ $asset->setFile($file);
+
+ $em->persist($asset);
+ $em->flush();
+
+ $template = $em->getRepository(Templates::class)->find($templateId);
+ if ($template) {
+ $template->setImage($asset);
+ $em->persist($template);
+ $em->flush();
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/CoreBundle/Repository/TemplatesRepository.php b/src/CoreBundle/Repository/TemplatesRepository.php
index fbd78015da..5560a852e8 100644
--- a/src/CoreBundle/Repository/TemplatesRepository.php
+++ b/src/CoreBundle/Repository/TemplatesRepository.php
@@ -55,4 +55,15 @@ class TemplatesRepository extends ServiceEntityRepository
return $qb->getQuery()->getResult();
}
+
+ public function findCourseDocumentTemplates(Course $course)
+ {
+ return $this->createQueryBuilder('t')
+ ->where('t.course = :course')
+ ->andWhere('t.refDoc IS NOT NULL')
+ ->andWhere('t.refDoc > 0')
+ ->setParameter('course', $course)
+ ->getQuery()
+ ->getResult();
+ }
}
diff --git a/src/CourseBundle/Entity/CDocument.php b/src/CourseBundle/Entity/CDocument.php
index e217f04283..b1061ec43f 100644
--- a/src/CourseBundle/Entity/CDocument.php
+++ b/src/CourseBundle/Entity/CDocument.php
@@ -181,6 +181,7 @@ class CDocument extends AbstractResource implements ResourceInterface, ResourceS
#[ORM\Column(name: 'readonly', type: 'boolean', nullable: false)]
protected bool $readonly;
+ #[Groups(['document:read', 'document:write'])]
#[ORM\Column(name: 'template', type: 'boolean', nullable: false)]
protected bool $template;