diff --git a/src/CoreBundle/Controller/ValidationTokenController.php b/src/CoreBundle/Controller/ValidationTokenController.php new file mode 100644 index 00000000000..c18f81000c0 --- /dev/null +++ b/src/CoreBundle/Controller/ValidationTokenController.php @@ -0,0 +1,119 @@ +tokenRepository->findOneBy([ + 'type' => $this->validationTokenHelper->getTypeId($type), + 'hash' => $hash + ]); + + if (!$token) { + throw $this->createNotFoundException('Invalid token.'); + } + + // Process the action related to the token type + $this->processAction($token); + + // Remove the used token + $this->tokenRepository->remove($token, true); + + // Register the token usage event + $this->registerTokenUsedEvent($token); + + return $this->render('@ChamiloCore/Validation/success.html.twig', [ + 'type' => $type, + ]); + } + + #[Route('/test/generate-token/{type}/{resourceId}', name: 'test_generate_token')] + public function testGenerateToken(string $type, int $resourceId): Response + { + $typeId = $this->validationTokenHelper->getTypeId($type); + $token = new ValidationToken($typeId, $resourceId); + $this->tokenRepository->save($token, true); + + $validationLink = $this->generateUrl('validate_token', [ + 'type' => $type, + 'hash' => $token->getHash(), + ], \Symfony\Component\Routing\Generator\UrlGeneratorInterface::ABSOLUTE_URL); + + return new Response("Generated token: {$token->getHash()}
Validation link: {$validationLink}"); + } + + private function processAction(ValidationToken $token): void + { + switch ($token->getType()) { + case 1: // Assuming 1 is for 'ticket' + $this->processTicketValidation($token); + break; + case 2: // Assuming 2 is for 'user' + // Implement user validation logic here + break; + default: + throw new \InvalidArgumentException('Unrecognized token type'); + } + } + + private function processTicketValidation(ValidationToken $token): void + { + $ticketId = $token->getResourceId(); + + // Simulate ticket validation logic + // Here you would typically check if the ticket exists and is valid + // For now, we'll just print a message to simulate this + // Replace this with your actual ticket validation logic + $ticketValid = $this->validateTicket($ticketId); + + if (!$ticketValid) { + throw new \RuntimeException('Invalid ticket.'); + } + + // If the ticket is valid, you can mark it as used or perform other actions + // For example, update the ticket status in the database + // $this->ticketRepository->markAsUsed($ticketId); + } + + private function validateTicket(int $ticketId): bool + { + // Here you would implement the logic to check if the ticket is valid. + // This is a placeholder function to simulate validation. + + // For testing purposes, let's assume all tickets are valid. + // In a real implementation, you would query your database or service. + + return true; // Assume the ticket is valid for now + } + + private function registerTokenUsedEvent(ValidationToken $token): void + { + $user = $this->security->getUser(); + $userId = $user?->getId(); + $this->trackEDefaultRepository->registerTokenUsedEvent($token, $userId); + } +} diff --git a/src/CoreBundle/Entity/TrackEDefault.php b/src/CoreBundle/Entity/TrackEDefault.php index d3e69f00700..43a372bd432 100644 --- a/src/CoreBundle/Entity/TrackEDefault.php +++ b/src/CoreBundle/Entity/TrackEDefault.php @@ -68,7 +68,7 @@ public function getDefaultUserId() return $this->defaultUserId; } - public function setCId(int $cId): self + public function setCId(?int $cId): self { $this->cId = $cId; @@ -153,7 +153,7 @@ public function getDefaultValue() return $this->defaultValue; } - public function setSessionId(int $sessionId): self + public function setSessionId(?int $sessionId): self { $this->sessionId = $sessionId; diff --git a/src/CoreBundle/Entity/ValidationToken.php b/src/CoreBundle/Entity/ValidationToken.php new file mode 100644 index 00000000000..f2c930a5397 --- /dev/null +++ b/src/CoreBundle/Entity/ValidationToken.php @@ -0,0 +1,96 @@ +type = $type; + $this->resourceId = $resourceId; + $this->hash = hash('sha256', uniqid((string) rand(), true)); + $this->createdAt = new \DateTime(); + } + + public function getId(): ?int + { + return $this->id; + } + + public function getType(): int + { + return $this->type; + } + + public function setType(int $type): self + { + $this->type = $type; + return $this; + } + + public function getResourceId(): int + { + return $this->resourceId; + } + + public function setResourceId(int $resourceId): self + { + $this->resourceId = $resourceId; + return $this; + } + + public function getHash(): string + { + return $this->hash; + } + + public function getCreatedAt(): \DateTime + { + return $this->createdAt; + } + + public function setCreatedAt(\DateTime $createdAt): self + { + $this->createdAt = $createdAt; + return $this; + } + + /** + * Genera un enlace de validaciĆ³n. + */ + public static function generateLink(int $type, int $resourceId): string + { + $token = new self($type, $resourceId); + return '/validate/' . $type . '/' . $token->getHash(); + } +} diff --git a/src/CoreBundle/Migrations/Schema/V200/Version20241211183300.php b/src/CoreBundle/Migrations/Schema/V200/Version20241211183300.php new file mode 100644 index 00000000000..4b9ad08f5a0 --- /dev/null +++ b/src/CoreBundle/Migrations/Schema/V200/Version20241211183300.php @@ -0,0 +1,42 @@ +hasTable('validation_token')) { + $this->addSql(" + CREATE TABLE validation_token ( + id INT AUTO_INCREMENT NOT NULL, + type INT NOT NULL, + resource_id BIGINT NOT NULL, + hash CHAR(64) NOT NULL, + created_at DATETIME NOT NULL COMMENT '(DC2Type:datetime)', + INDEX idx_type_hash (type, hash), + PRIMARY KEY(id) + ) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB ROW_FORMAT = DYNAMIC + "); + } + } + + public function down(Schema $schema): void + { + if ($schema->hasTable('validation_token')) { + $this->addSql('DROP TABLE validation_token'); + } + } +} diff --git a/src/CoreBundle/Repository/TrackEDefaultRepository.php b/src/CoreBundle/Repository/TrackEDefaultRepository.php index 0a10a41bbe7..bfaecf3b056 100644 --- a/src/CoreBundle/Repository/TrackEDefaultRepository.php +++ b/src/CoreBundle/Repository/TrackEDefaultRepository.php @@ -7,6 +7,7 @@ namespace Chamilo\CoreBundle\Repository; use Chamilo\CoreBundle\Entity\TrackEDefault; +use Chamilo\CoreBundle\Entity\ValidationToken; use DateTime; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; use Doctrine\Persistence\ManagerRegistry; @@ -65,4 +66,22 @@ public function getUserCourseRegistrationAt(int $courseId, int $userId, ?int $se return null; } + + /** + * Registers an event when a validation token is used. + */ + public function registerTokenUsedEvent(ValidationToken $token, ?int $userId = null): void + { + $event = new TrackEDefault(); + $event->setDefaultUserId($userId ?? 0); + $event->setCId(null); + $event->setDefaultDate(new \DateTime()); + $event->setDefaultEventType('VALIDATION_TOKEN_USED'); + $event->setDefaultValueType('validation_token'); + $event->setDefaultValue(\json_encode(['hash' => $token->getHash()])); + $event->setSessionId(null); + + $this->_em->persist($event); + $this->_em->flush(); + } } diff --git a/src/CoreBundle/Repository/ValidationTokenRepository.php b/src/CoreBundle/Repository/ValidationTokenRepository.php new file mode 100644 index 00000000000..9edb35630f0 --- /dev/null +++ b/src/CoreBundle/Repository/ValidationTokenRepository.php @@ -0,0 +1,37 @@ +getEntityManager()->persist($entity); + + if ($flush) { + $this->getEntityManager()->flush(); + } + } + + public function remove(ValidationToken $entity, bool $flush = false): void + { + $this->getEntityManager()->remove($entity); + + if ($flush) { + $this->getEntityManager()->flush(); + } + } +} diff --git a/src/CoreBundle/Resources/views/Validation/success.html.twig b/src/CoreBundle/Resources/views/Validation/success.html.twig new file mode 100644 index 00000000000..7b9aded6f11 --- /dev/null +++ b/src/CoreBundle/Resources/views/Validation/success.html.twig @@ -0,0 +1,6 @@ +{% extends "@ChamiloCore/Layout/layout_one_col.html.twig" %} + +{% block content %} +

Validation Successful

+

The token for {{ type }} has been successfully validated.

+{% endblock %} diff --git a/src/CoreBundle/ServiceHelper/ValidationTokenHelper.php b/src/CoreBundle/ServiceHelper/ValidationTokenHelper.php new file mode 100644 index 00000000000..b04549a61af --- /dev/null +++ b/src/CoreBundle/ServiceHelper/ValidationTokenHelper.php @@ -0,0 +1,48 @@ +tokenRepository->save($token, true); + + return $this->urlGenerator->generate('validate_token', [ + 'type' => $this->getTypeString($type), + 'hash' => $token->getHash(), + ], UrlGeneratorInterface::ABSOLUTE_URL); + } + + public function getTypeId(string $type): int + { + return match ($type) { + 'ticket' => 1, + 'user' => 2, + default => throw new \InvalidArgumentException('Unrecognized validation type'), + }; + } + + private function getTypeString(int $type): string + { + return match ($type) { + 1 => 'ticket', + 2 => 'user', + default => throw new \InvalidArgumentException('Unrecognized validation type'), + }; + } +}