diff --git a/CHANGELOG.md b/CHANGELOG.md index 0cd4d5f..5ed901e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ See [keep a changelog] for information about writing changes to this log. ## [Unreleased] +[PR-25](https://github.com/itk-dev/itqr/pull/25) + - Qr hit counter [PR-23](https://github.com/itk-dev/itqr/pull/23) - Seperate visual representation of QR into own config - Live preview of design and download diff --git a/migrations/Version20250613101601.php b/migrations/Version20250613101601.php new file mode 100644 index 0000000..5173396 --- /dev/null +++ b/migrations/Version20250613101601.php @@ -0,0 +1,33 @@ +addSql('CREATE TABLE qr_hit_tracker (id INT AUTO_INCREMENT NOT NULL, qr_id INT DEFAULT NULL, timestamp DATETIME NOT NULL, INDEX IDX_E53FF9E35AA64A57 (qr_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE `utf8_unicode_ci` ENGINE = InnoDB'); + $this->addSql('ALTER TABLE qr_hit_tracker ADD CONSTRAINT FK_E53FF9E35AA64A57 FOREIGN KEY (qr_id) REFERENCES qr (id)'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE qr_hit_tracker DROP FOREIGN KEY FK_E53FF9E35AA64A57'); + $this->addSql('DROP TABLE qr_hit_tracker'); + } +} diff --git a/src/Controller/Admin/QrCrudController.php b/src/Controller/Admin/QrCrudController.php index 6340c79..caaa6c3 100644 --- a/src/Controller/Admin/QrCrudController.php +++ b/src/Controller/Admin/QrCrudController.php @@ -5,6 +5,7 @@ use App\Controller\Admin\Embed\UrlCrudController; use App\Entity\Tenant\Qr; use App\Helper\DownloadHelper; +use App\Repository\QrHitTrackerRepository; use EasyCorp\Bundle\EasyAdminBundle\Config\Action; use EasyCorp\Bundle\EasyAdminBundle\Config\Actions; use EasyCorp\Bundle\EasyAdminBundle\Config\Assets; @@ -17,6 +18,7 @@ use EasyCorp\Bundle\EasyAdminBundle\Field\CollectionField; use EasyCorp\Bundle\EasyAdminBundle\Field\Field; use EasyCorp\Bundle\EasyAdminBundle\Field\IdField; +use EasyCorp\Bundle\EasyAdminBundle\Field\IntegerField; use EasyCorp\Bundle\EasyAdminBundle\Field\TextEditorField; use EasyCorp\Bundle\EasyAdminBundle\Field\TextField; use EasyCorp\Bundle\EasyAdminBundle\Filter\ChoiceFilter; @@ -32,6 +34,7 @@ class QrCrudController extends AbstractTenantAwareCrudController { public function __construct( private readonly DownloadHelper $downloadHelper, + private readonly QrHitTrackerRepository $hitTrackerRepository, ) { } @@ -67,6 +70,15 @@ public function configureFields(string $pageName): iterable Field::new('customUrlButton', new TranslatableMessage('qr.preview')) ->setTemplatePath('fields/link/link.html.twig') ->hideOnForm(), + IntegerField::new('hitTrackers', new TranslatableMessage('Hits')) + ->formatValue(function ($value, $entity) { + if (null === $entity) { + return '0'; + } + + return $this->hitTrackerRepository->getHitCount($entity); + }) + ->hideOnForm(), ]; } diff --git a/src/Controller/QrController.php b/src/Controller/QrController.php index aaef2c6..4731bc2 100644 --- a/src/Controller/QrController.php +++ b/src/Controller/QrController.php @@ -2,8 +2,10 @@ namespace App\Controller; +use App\Entity\QrHitTracker; use App\Repository\QrRepository; use App\Repository\UrlRepository; +use Doctrine\ORM\EntityManagerInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Response; @@ -18,7 +20,7 @@ public function __construct( } #[Route('/qr/{uuid}', name: 'app_qr_index')] - public function index(string $uuid, UrlRepository $urlRepository): Response + public function index(string $uuid, UrlRepository $urlRepository, EntityManagerInterface $entityManager): Response { // Find the QR entity by UUID $uuid = UuidV7::fromString($uuid); @@ -28,6 +30,13 @@ public function index(string $uuid, UrlRepository $urlRepository): Response throw $this->createNotFoundException('QR code not found'); } + // Create QR hit tracker entry + $qrHitTracker = new QrHitTracker(); + $qrHitTracker->setQr($qr); + $qrHitTracker->setTimestamp(new \DateTimeImmutable()); + $entityManager->persist($qrHitTracker); + $entityManager->flush(); + $urls = $qr->getUrls(); // @TODO: Add what happens if a qr has multiple urls with certain modes. diff --git a/src/Entity/QrHitTracker.php b/src/Entity/QrHitTracker.php new file mode 100644 index 0000000..b7c9763 --- /dev/null +++ b/src/Entity/QrHitTracker.php @@ -0,0 +1,53 @@ +id; + } + + public function getQr(): Qr + { + return $this->qr; + } + + public function setQr(Qr $qr): static + { + $this->qr = $qr; + + return $this; + } + + public function getTimestamp(): ?\DateTimeInterface + { + return $this->timestamp; + } + + public function setTimestamp(\DateTimeInterface $timestamp): static + { + $this->timestamp = $timestamp; + + return $this; + } +} diff --git a/src/Entity/Tenant/Qr.php b/src/Entity/Tenant/Qr.php index c58632f..c34a814 100644 --- a/src/Entity/Tenant/Qr.php +++ b/src/Entity/Tenant/Qr.php @@ -6,6 +6,7 @@ use ApiPlatform\Doctrine\Orm\Filter\SearchFilter; use ApiPlatform\Metadata\ApiFilter; use ApiPlatform\Metadata\ApiResource; +use App\Entity\QrHitTracker; use App\Enum\QrModeEnum; use App\Repository\QrRepository; use Doctrine\Common\Collections\ArrayCollection; @@ -45,11 +46,18 @@ class Qr extends AbstractTenantScopedEntity #[Assert\Valid] private Collection $urls; + /** + * @var Collection + */ + #[ORM\OneToMany(targetEntity: QrHitTracker::class, mappedBy: 'qr')] + private Collection $hitTrackers; + public function __construct() { parent::__construct(); $this->urls = new ArrayCollection(); + $this->hitTrackers = new ArrayCollection(); $this->uuid = Uuid::v7(); } @@ -143,4 +151,9 @@ public function removeAllUrls(): void $this->removeUrl($url); } } + + public function getHitTrackers(): Collection + { + return $this->hitTrackers; + } } diff --git a/src/Repository/QrHitTrackerRepository.php b/src/Repository/QrHitTrackerRepository.php new file mode 100644 index 0000000..9607b92 --- /dev/null +++ b/src/Repository/QrHitTrackerRepository.php @@ -0,0 +1,24 @@ + + */ +class QrHitTrackerRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, QrHitTracker::class); + } + + public function getHitCount(Qr $qr): int + { + return $this->count(['qr' => $qr]); + } +} diff --git a/templates/fields/link/link.html.twig b/templates/fields/link/link.html.twig index 1c12ee6..220d058 100644 --- a/templates/fields/link/link.html.twig +++ b/templates/fields/link/link.html.twig @@ -1,4 +1,6 @@ - +{% set qr_path = path('qr_code_generate', {builder: 'default', data: url('app_qr_index', {uuid: entity.instance.uuid})}) %} + + {{ 'qr.view'|trans }} Generated Image