Commit 31109ada authored by Johnny Leveneur's avatar Johnny Leveneur
Browse files

initialisation module qr

parent 97fbc34c
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -47,6 +47,7 @@ $modules = [
    'Atelier',
    'Application',
    'Personnel',
    'Qr',

//    'UnicaenVerification',
];
+57 −0
Original line number Diff line number Diff line
<video id="preview" autoplay playsinline style="width: 320px; height:auto; border:1px solid #ccc;"></video>
<div id="status">Initialisation caméra…</div>

<script type="module">
    import { BrowserMultiFormatReader } from "https://cdn.jsdelivr.net/npm/@zxing/library@0.20.0/+esm";

    const statusEl = document.getElementById('status');
    const videoEl  = document.getElementById('preview');
    const codeReader = new BrowserMultiFormatReader();

    async function start() {
        try {
            // Choix auto de la caméra arrière si dispo (smartphones)
            const devices = await navigator.mediaDevices.enumerateDevices();
            const videoInputs = devices.filter(d => d.kind === 'videoinput');
            const backCam = videoInputs.find(d => /back|rear|environment/i.test(d.label)) || videoInputs[0];

            const constraints = backCam
                ? { video: { deviceId: backCam.deviceId } }
                : { video: { facingMode: { ideal: "environment" } } };

            const stream = await navigator.mediaDevices.getUserMedia(constraints);
            videoEl.srcObject = stream;

            statusEl.textContent = "Recherche du QR code…";
            // Décodage continu depuis la webcam
            await codeReader.decodeFromVideoDevice(backCam?.deviceId ?? null, videoEl, async (result, err) => {
                if (result) {
                    statusEl.textContent = "QR détecté : " + result.getText();
                    // (Optionnel) envoyer à PHP
                    try {
                        await fetch('/qr/callback', {
                            method: 'POST',
                            headers: { 'Content-Type': 'application/json' },
                            body: JSON.stringify({ payload: result.getText() })
                        });
                    } catch (e) { /* ignore réseau */ }

                    // Si vous voulez stopper après 1 lecture :
                    // codeReader.reset();
                    // stream.getTracks().forEach(t => t.stop());
                } else if (err && !(err instanceof ZXing.NotFoundException)) {
                    // Erreurs non « pas trouvé »
                    console.warn(err);
                }
            });
        } catch (e) {
            statusEl.textContent = "Erreur caméra : " + (e.message || e);
        }
    }

    if (navigator.mediaDevices?.getUserMedia) {
        start();
    } else {
        statusEl.textContent = "getUserMedia non supporté par ce navigateur.";
    }
</script>

module/Qr/Module.php

0 → 100644
+56 −0
Original line number Diff line number Diff line
<?php

namespace Qr;


use Laminas\Config\Factory as ConfigFactory;
use Laminas\Http\Request as HttpRequest;
use Laminas\Mvc\ModuleRouteListener;
use Laminas\Mvc\MvcEvent;
use Laminas\Stdlib\ArrayUtils;
use Laminas\Stdlib\Glob;

class Module
{
    public function onBootstrap(MvcEvent $e)
    {
        $e->getApplication()->getServiceManager()->get('translator');
        $eventManager        = $e->getApplication()->getEventManager();
        $moduleRouteListener = new ModuleRouteListener();
        $moduleRouteListener->attach($eventManager);

        /* Active un layout spécial si la requête est de type AJAX. Valable pour TOUS les modules de l'application. */
        $eventManager->getSharedManager()->attach('Laminas\Mvc\Controller\AbstractActionController', 'dispatch',
            function (MvcEvent $e) {
                $request = $e->getRequest();
                if ($request instanceof HttpRequest && $request->isXmlHttpRequest()) {
                    $e->getTarget()->layout('layout/ajax.phtml');
                }
            }
        );
    }

    public function getConfig()
    {
        $configInit = [
            __DIR__ . '/config/module.config.php'
        ];
        $configFiles = ArrayUtils::merge(
            $configInit,
            Glob::glob(__DIR__ . '/config/merged/{,*.}{config}.php', Glob::GLOB_BRACE)
        );

        return ConfigFactory::fromFiles($configFiles);
    }

    public function getAutoloaderConfig()
    {
        return array(
            'Laminas\Loader\StandardAutoloader' => array(
                'namespaces' => array(
                    __NAMESPACE__ => __DIR__ . '/src/',
                ),
            ),
        );
    }
}
+106 −0
Original line number Diff line number Diff line
<?php

use Doctrine\ORM\Mapping\Driver\XmlDriver;
use Doctrine\Persistence\Mapping\Driver\MappingDriverChain;
use Laminas\Router\Http\Literal;
use Qr\Controller\ScanController;
use UnicaenPrivilege\Guard\PrivilegeController;

return array(
    'doctrine' => [
        'driver' => [
            'orm_default' => [
                'class' => MappingDriverChain::class,
                'drivers' => [
                    'Personnel\Entity\Db' => 'orm_default_xml_driver',
                ],
            ],
            'orm_default_xml_driver' => [
                'class' => XmlDriver::class,
                'cache' => 'apc',
                'paths' => [
                    __DIR__ . '/../src/Entity/Db/Mapping',
                ],
            ],
        ],
        'cache' => [
            'apc' => [
                'namespace' => 'MCP__' . __NAMESPACE__,
            ],
        ],
    ],

    'bjyauthorize' => [
        'guards' => [
            PrivilegeController::class => [
                [
                    'controller' => ScanController::class,
                    'action' => [
                        'index', 'index-catalogue'
                    ],
                    'roles' => 'guest'
                ],
            ],
        ],
    ],

    'router' => [
        'routes' => [
            'qr-scan' => [
                'type' => Literal::class,
                'options' => [
                    'route' => '/qr/scan',
                    'defaults' => [
                        'controller' => ScanController::class,
                        'action' => 'index',
                    ],
                ],
            ],
            'qr-callback' => [
                'type' => Literal::class,
                'options' => [
                    'route' => '/qr/callback',
                    'defaults' => [
                        'controller' => ScanController::class,
                        'action' => 'callback',
                    ],
                ],
                'may_terminate' => true,
                'child_routes' => [],
            ],
        ],
    ],

    'service_manager' => [
        'factories' => [
        ],
    ],
    'form_elements' => [
        'factories' => [
        ],
    ],
    'hydrators' => [
        'invokables' => [
        ],
    ],
    'controllers' => [
        'factories' => [
            ScanController::class => function ($container) {
                return new ScanController();
            },
        ],
    ],
    'view_helpers' => [
        'invokables' => [
        ],
    ],

    'view_manager' => [
        'template_path_stack' => [
            __DIR__ . '/../view',
        ],
        'strategies' => [
            'ViewJsonStrategy', // pour renvoyer du JSON facilement
        ],
    ],
);
+59 −0
Original line number Diff line number Diff line
<?php
namespace Qr\Controller;

use Laminas\Mvc\Controller\AbstractActionController;
use Laminas\View\Model\ViewModel;
use Laminas\View\Model\JsonModel;

class ScanController extends AbstractActionController
{
    /**
     * GET /qr/scan
     * Affiche la page avec le flux webcam + décodage client.
     */
    public function indexAction()
    {
        // Tu peux passer des variables à la vue si besoin :
        return new ViewModel([
            'callbackUrl' => $this->url()->fromRoute('qr-callback'),
        ]);
    }

    /**
     * POST /qr/callback
     * Reçoit le payload décodé côté navigateur et renvoie un JSON.
     */
    public function callbackAction()
    {
        $request = $this->getRequest();
        if (! $request->isPost()) {
            return new JsonModel(['ok' => false, 'error' => 'Méthode non autorisée'], 405);
        }

        // Récupération JSON brute
        $raw = $request->getContent();
        $data = json_decode($raw, true);

        if (!is_array($data)) {
            return new JsonModel(['ok' => false, 'error' => 'JSON invalide']);
        }

        $payload = $data['payload'] ?? null;
        if (!$payload || !is_string($payload)) {
            return new JsonModel(['ok' => false, 'error' => 'payload manquant']);
        }

        // 👉 ICI : traite le contenu du QR (lookup DB, validation billet, etc.)
        // Exemple : valider un ticket "EVT-XXXX"
        $valid = (bool) preg_match('/^EVT-[A-Z0-9]{4,}$/', $payload);

        // Tu peux logguer / enregistrer en base, etc.
        // $this->someService->handleQrPayload($payload);

        return new JsonModel([
            'ok'      => true,
            'valid'   => $valid,
            'payload' => $payload,
        ]);
    }
}
Loading