diff --git a/.env b/.env index 5450e90..8c95c73 100644 --- a/.env +++ b/.env @@ -16,33 +16,80 @@ ###> symfony/framework-bundle ### APP_ENV=dev -APP_SECRET= +APP_SECRET=a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6 APP_SHARE_DIR=var/share ###< symfony/framework-bundle ### ###> symfony/routing ### -# Configure how to generate URLs in non-HTTP contexts, such as CLI commands. -# See https://symfony.com/doc/current/routing.html#generating-urls-in-commands -DEFAULT_URI=http://localhost +DEFAULT_URI=https://esyweb.local ###< symfony/routing ### ###> doctrine/doctrine-bundle ### -# Format described at https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url -# IMPORTANT: You MUST configure your server version, either here or in config/packages/doctrine.yaml -# -# DATABASE_URL="sqlite:///%kernel.project_dir%/var/data_%kernel.environment%.db" -# DATABASE_URL="mysql://app:!ChangeMe!@127.0.0.1:3306/app?serverVersion=8.0.32&charset=utf8mb4" -# DATABASE_URL="mysql://app:!ChangeMe!@127.0.0.1:3306/app?serverVersion=10.11.2-MariaDB&charset=utf8mb4" -DATABASE_URL="postgresql://app:!ChangeMe!@127.0.0.1:5432/app?serverVersion=16&charset=utf8" +DATABASE_URL="postgresql://app:secret@pgbouncer:6432/e_ticket?serverVersion=16&charset=utf8" ###< doctrine/doctrine-bundle ### ###> symfony/messenger ### -# Choose one of the transports below -# MESSENGER_TRANSPORT_DSN=amqp://guest:guest@localhost:5672/%2f/messages -# MESSENGER_TRANSPORT_DSN=redis://localhost:6379/messages -MESSENGER_TRANSPORT_DSN=doctrine://default?auto_setup=0 +MESSENGER_TRANSPORT_DSN=redis://redis:6379/messages ###< symfony/messenger ### +###> session ### +SESSION_HANDLER_DSN=redis://redis:6379/1 +###< session ### + +###> cache ### +REDIS_CACHE_DSN=redis://redis:6379/2 +###< cache ### + ###> symfony/mailer ### -MAILER_DSN=null://null +MAILER_DSN=smtp://mailpit:1025 ###< symfony/mailer ### + +###> vite ### +# 0 = dev (HMR via localhost:5173), 1 = prod (manifest build) +VITE_LOAD=0 +REAL_MAIL=0 +###< vite ### +STRIPE_PK= +STRIPE_SK= +STRIPE_WEBHOOK_SECRET= +STRIPE_WEBHOOK_SECRET_CONNECT= +STRIPE_MODE=test +STRIPE_FEE_RATE=0.015 +STRIPE_FEE_FIXED=25 +ADMIN_EMAIL=contact@e-cosplay.fr + +###> SonarQube ### +SONARQUBE_URL=https://sn.esy-web.dev +SONARQUBE_BADGE_TOKEN= +SONARQUBE_PROJECT_KEY=crm-ecosplay +###< SonarQube ### + +###> SSO E-Cosplay (Keycloak OIDC) ### +OAUTH_KEYCLOAK_CLIENT_ID=e-ticket +OAUTH_KEYCLOAK_CLIENT_SECRET=changeme +OAUTH_KEYCLOAK_URL=https://auth.esy-web.dev +OAUTH_KEYCLOAK_REALM=e-cosplay +###< SSO E-Cosplay (Keycloak OIDC) ### + +###> Keycloak Admin Service Account ### +KEYCLOAK_ADMIN_CLIENT_ID=crm-ecosplay-admin +KEYCLOAK_ADMIN_CLIENT_SECRET= +###< Keycloak Admin Service Account ### + +###> symfony/amazon-mailer ### +# MAILER_DSN=ses://ACCESS_KEY:SECRET_KEY@default?region=eu-west-1 +# MAILER_DSN=ses+smtp://ACCESS_KEY:SECRET_KEY@default?region=eu-west-1 +###< symfony/amazon-mailer ### + +###> stripe/stripe-php ### +STRIPE_SECRET_KEY=sk_test_*** +###< stripe/stripe-php ### +SMIME_PASSPHRASE=EVz5zNV8h4ndSLOCWO9JeaQnIertQm7k +SECRET_ANALYTICS= + +###> docuseal ### +DOCUSEAL_URL=https://signature.esy-web.dev +DOCUSEAL_API= +DOCUSEAL_WEBHOOKS_SECRET_HEADER=X-Sign +DOCUSEAL_WEBHOOKS_SECRET= +###< docuseal ### diff --git a/.gitea/.gitkeep b/.gitea/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/.gitea/CODEOWNERS b/.gitea/CODEOWNERS new file mode 100644 index 0000000..3f559b2 --- /dev/null +++ b/.gitea/CODEOWNERS @@ -0,0 +1 @@ +* @jovann @e-cosplay diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml new file mode 100644 index 0000000..fb48a29 --- /dev/null +++ b/.gitea/workflows/ci.yml @@ -0,0 +1,353 @@ + + j'ai gitea + sonarqube auto hébger tu me conseiler quoi en plus pour analyser le code php + + 13:35 + Bonne question ! Avec Gitea + SonarQube déjà en place, voici ce que je te recommanderais pour compléter ta chaîne d'analyse PHP : + + Analyse statique complémentaire — Tu utilises déjà PHPStan via SonarQube, mais le lancer aussi en standalone (niveau max) te donne un feedback plus rapide dans ta CI. Psalm est une alternative/complément intéressant, surtout pour l'analyse de type et la détection de dead code. Les deux peuvent coexister. + + Style & standards — PHP CS Fixer ou PHP_CodeSniffer pour enforcer un coding standard (PSR-12 ou custom). PHP CS Fixer s'intègre bien dans un pre-commit hook ou un job CI Gitea Actions/Drone. + + Sécurité — Snyk ou Trivy pour scanner les dépendances Composer (CVE). Trivy est self-hostable et peut aussi scanner tes images Docker, ce qui colle bien avec ton stack. Semgrep est aussi un bon choix pour des règles de sécurité custom sur le code PHP (OWASP patterns, injections SQL, XSS). + + Complexité & dette technique — PHPMetrics génère des rapports HTML visuels sur la complexité cyclomatique, le couplage, la maintenabilité. Ça complète bien SonarQube avec une vue plus "architecturale". dePHPend analyse les dépendances entre classes/packages pour détecter les couplages problématiques. + + Tests & couverture — Si pas déjà en place, PHPUnit + Infection (mutation testing) pour mesurer la qualité réelle de tes tests, pas juste le coverage brut. SonarQube peut ingérer les rapports de coverage PHPUnit. + + En priorité pour ton contexte (multi-tenant, ~85 sites ESY-WEB), je partirais sur : + + PHPStan niveau max en CI (si pas déjà fait en standalone) + Trivy (scan dépendances Composer + images Docker — tu as déjà le stack Docker) + PHP CS Fixer en pre-commit + PHPMetrics pour le reporting visuel + Tout ça s'orchestre bien avec des Gitea Actions ou un pipeline Makefile, ce qui colle avec ton workflow habituel. Tu veux que je t'aide à mettre en place un de ces outils en particulier ? + + + name: CI on: push: branches: - master - main pull_request: jobs: sonarqube: runs_on: ubuntu-latest services: database: image: postgres:16-alpine env: POSTGRES_USER: app POSTGRES_PASSWORD: secret POS + + pasted + + j'ai deja tout sa + + 13:36 + Ah ok, t'es déjà bien équipé ! Ton pipeline est solide. Voici ce qui manque et qui aurait une vraie valeur ajoutée : + + Sécurité applicative — Semgrep pour de l'analyse SAST orientée patterns PHP (injections SQL, XSS, SSRF, deserialize unsafe). OWASP Dependency-Check couvre les dépendances, mais pas le code lui-même. Semgrep est gratuit en CLI et s'ajoute en une step. + + Qualité / dette — PHPMetrics pour un rapport HTML visuel (complexité cyclomatique, couplage, maintenabilité par classe). SonarQube donne des métriques mais PHPMetrics a une vue "bubble chart" par classe qui aide à prioriser le refacto. Très rapide à ajouter, pas besoin de services. + + Tests — Infection (mutation testing). Tu as déjà PHPUnit + coverage, mais le coverage seul ne dit pas si tes tests détectent vraiment les régressions. Infection mute ton code et vérifie que les tests cassent. Ça s'intègre bien avec le coverage.xml que tu génères déjà. + + Infra/Docker — Trivy en remplacement ou complément de Hadolint. Hadolint lint les Dockerfiles, Trivy scanne les images buildées (CVE dans les packages OS + dépendances). Vu ton stack multi-container c'est pertinent. + + Frontend — Lighthouse CI si tu as des pages publiques côté E-Cosplay. Perf, accessibilité, SEO en une step. + + En résumé les ajouts concrets : + + yaml + - name: Semgrep SAST + run: | + pip install semgrep --break-system-packages + semgrep scan --config=auto --json -o semgrep-report.json src/ || true + + - name: PHPMetrics + run: | + composer global require phpmetrics/phpmetrics --no-interaction + phpmetrics --report-html=phpmetrics-report src/ + + - name: Infection (mutation testing) + run: | + vendor/bin/infection --threads=4 --min-msi=50 --min-covered-msi=60 --logger-json=infection-report.json || true + Semgrep est le plus impactant vu que t'as zéro SAST sur le code source actuellement — OWASP DC ne couvre que les dépendances. Tu veux que je t'intègre ça proprement dans ton workflow complet ? + + + name: CI on: push: branches: - master - main pull_request: jobs: sonarqube: runs_on: ubuntu-latest services: database: image: postgres:16-alpine env: POSTGRES_USER: app POSTGRES_PASSWORD: secret POS + + pasted + + 13:37 + Nickel, t'as intégré les trois. Ton pipeline est maintenant très complet. Quelques remarques d'optimisation : + + Problème potentiel avec Infection — il a besoin du coverage.xml généré par PHPUnit, mais il faut lui passer explicitement. Vérifie que ton infection.json5 pointe bien dessus, sinon ajoute --coverage=coverage.xml ou mieux, utilise le flag --skip-initial-tests si tu veux qu'il réutilise le coverage existant. + + PHPMetrics — composer global require peut être lent en CI. Une alternative plus rapide : + + yaml + - name: PHPMetrics + run: | + wget -qO phpmetrics.phar https://github.com/phpmetrics/PhpMetrics/releases/latest/download/phpmetrics.phar + php phpmetrics.phar --report-html=phpmetrics-report src/ + Semgrep — pip install en CI c'est aussi un peu lent. L'image Docker officielle est plus rapide : + + yaml + - name: Semgrep SAST + run: | + docker run --rm -v "${GITHUB_WORKSPACE}:/src" semgrep/semgrep semgrep scan --config=auto --json -o /src/semgrep-report.json /src/src/ || true + Mais vu que t'es sur Gitea Actions (pas GitHub), vérifie que le Docker-in-Docker fonctionne dans ton runner. Sinon le pip install reste le plus safe. + + Ce qui manque pour boucler — tous ces rapports (semgrep, phpmetrics, infection, hadolint) sont générés mais pas exploités. Deux options : + + SonarQube — configure sonar.externalIssuesReportPaths dans ton sonar-project.properties pour ingérer les JSON de Semgrep et Hadolint. SonarQube les affichera comme "external issues". + Artifacts — upload les rapports comme artifacts de build pour les consulter après : + yaml + - name: Upload reports + uses: actions/upload-artifact@v3 + with: + name: ci-reports + path: | + semgrep-report.json + phpmetrics-report/ + infection-report.json + hadolint-*.json + dependency-check-report.* + eslint-report.json + phpstan-report.json + Ça te permettrait de consulter le rapport PHPMetrics HTML directement. Tu veux que je te génère le sonar-project.properties adapté avec l'import des rapports externes ? + + + name: CI on: push: branches: - master - main pull_request: jobs: sonarqube: runs_on: ubuntu-latest services: database: image: postgres:16-alpine env: POSTGRES_USER: app POSTGRES_PASSWORD: secret POS + + pasted + + 13:38 + OK, quelques corrections à faire sur ton workflow actuel : + + PHPMetrics — tu mélanges les deux approches. Tu fais composer global require mais ensuite php phpmetrics.phar (qui n'existe pas puisque t'as pas téléchargé le phar). Choisis l'un ou l'autre : + + yaml + # Option A : phar (plus rapide) + - name: PHPMetrics + run: | + wget -qO phpmetrics.phar https://github.com/phpmetrics/PhpMetrics/releases/latest/download/phpmetrics.phar + php phpmetrics.phar --report-html=phpmetrics-report src/ + + # Option B : composer global + - name: PHPMetrics + run: | + composer global require phpmetrics/phpmetrics --no-interaction + ~/.composer/vendor/bin/phpmetrics --report-html=phpmetrics-report src/ + Semgrep Docker — attention, sur Gitea Actions le ${GITHUB_WORKSPACE} n'existe probablement pas. Selon ton runner Gitea, ça sera plutôt ${GITEA_WORKSPACE} ou ${{ github.workspace }} (si ton runner est compatible). Vérifie, sinon reviens au pip install qui est plus portable : + + yaml + - name: Semgrep SAST + run: | + pip install semgrep --break-system-packages + semgrep scan --config=auto --json -o semgrep-report.json src/ || true + Upload des artifacts + sonar-project.properties — tu veux que je te génère les deux fichiers ? Le sonar-project.properties avec l'import de tous tes rapports externes (Semgrep, PHPStan, ESLint, Hadolint, coverage), et la step d'upload artifacts à ajouter en fin de pipeline ? + + sort tout le workflow stp + + 13:38 + Voici le workflow complet corrigé : + + + Presenting files + Presenting files + + Workflow CI complet corrigé avec tous les outils d'analyse + ci.yml + + sonar-project.properties avec import de tous les rapports externes + sonar-project.properties + + Presenting file(s)... + + + + Claude est une IA et peut faire des erreurs. Veuillez vérifier les réponses. + Ci · YML + Copier + + name: CI + + on: + push: + branches: + - master + - main + pull_request: + + jobs: + ci: + runs_on: ubuntu-latest + services: + database: + image: postgres:16-alpine + env: + POSTGRES_USER: app + POSTGRES_PASSWORD: secret + POSTGRES_DB: crm_ecosplay + options: >- + --health-cmd "pg_isready -U app -d crm_ecosplay" + --health-interval 5s + --health-timeout 5s + --health-retries 5 + redis: + image: redis:7-alpine + options: >- + --health-cmd "redis-cli ping" + --health-interval 5s + --health-timeout 5s + --health-retries 5 + meilisearch: + image: getmeili/meilisearch:latest + env: + MEILI_MASTER_KEY: test + MEILI_ENV: development + env: + DATABASE_URL: "postgresql://app:secret@database:5432/crm_ecosplay?serverVersion=16&charset=utf8" + MESSENGER_TRANSPORT_DSN: "redis://redis:6379/messages" + MAILER_DSN: "null://null" + MEILISEARCH_URL: "http://meilisearch:7700" + MEILISEARCH_API_KEY: "test" + APP_ENV: test + + steps: + # ─── Setup ─────────────────────────────────────────────── + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.4' + extensions: intl, pdo_pgsql, zip, gd, redis, imagick + coverage: xdebug + + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: '22' + + - name: Setup Bun + uses: oven-sh/setup-bun@v1 + + # ─── Dependencies ──────────────────────────────────────── + - name: Install PHP dependencies + run: composer install --no-interaction --prefer-dist + + - name: Install JS dependencies + run: | + bun install + npm install + + # ─── Security audits ───────────────────────────────────── + - name: Composer security audit + run: composer audit + + # ─── Linting ───────────────────────────────────────────── + - name: Lint Twig + run: php bin/console lint:twig templates/ + + - name: Lint YAML + run: php bin/console lint:yaml config/ + + - name: Lint Container + run: php bin/console lint:container + + - name: PHP CS Fixer + run: vendor/bin/php-cs-fixer fix --dry-run --diff + + - name: ESLint report + run: bunx eslint assets/ --ext .js,.ts -f json -o eslint-report.json || true + + - name: Stylelint + run: bunx stylelint "assets/**/*.scss" + + # ─── Static analysis ───────────────────────────────────── + - name: PHPStan report + run: vendor/bin/phpstan analyse src/ --level=6 --no-progress --error-format=json > phpstan-report.json || true + + - name: Semgrep SAST + run: | + pip install semgrep --break-system-packages + semgrep scan --config=auto --json -o semgrep-report.json src/ || true + + # ─── Build ─────────────────────────────────────────────── + - name: Build assets + run: bun run build + + # ─── Tests ─────────────────────────────────────────────── + - name: JS tests with coverage + run: bun run test:coverage + + - name: Create test database + run: php bin/console doctrine:database:create --env=test --if-not-exists + + - name: Create database schema + run: php bin/console doctrine:schema:create --env=test + + - name: PHPUnit with coverage + run: vendor/bin/phpunit --testdox --coverage-clover coverage.xml --coverage-text + + - name: Infection (mutation testing) + run: | + vendor/bin/infection \ + --threads=4 \ + --skip-initial-tests \ + --coverage=. \ + --min-msi=50 \ + --min-covered-msi=60 \ + --logger-json=infection-report.json || true + + # ─── Metrics ───────────────────────────────────────────── + - name: PHPMetrics + run: | + wget -qO phpmetrics.phar https://github.com/phpmetrics/PhpMetrics/releases/latest/download/phpmetrics.phar + php phpmetrics.phar --report-html=phpmetrics-report src/ + + # ─── Docker linting ────────────────────────────────────── + - name: Hadolint + run: | + wget -qO hadolint https://github.com/hadolint/hadolint/releases/latest/download/hadolint-Linux-x86_64 + chmod +x hadolint + ./hadolint docker/php/dev/Dockerfile -f json > hadolint-dev.json || true + ./hadolint docker/php/prod/Dockerfile -f json > hadolint-prod.json || true + + # ─── Dependency vulnerability scan ─────────────────────── + - name: OWASP Dependency-Check + uses: dependency-check/Dependency-Check_Action@main + with: + project: 'crm-ecosplay' + path: '.' + format: 'JSON,HTML' + args: > + --scan composer.lock + --scan package.json + --out . + --disableAssembly + --nvdApiKey ${{ secrets.NVD_API_KEY }} + continue-on-error: true + + - name: Rename Dependency-Check reports + run: | + mv dependency-check-report.json dc-report.json 2>/dev/null || true + mv dependency-check-report.html dc-report.html 2>/dev/null || true + + # ─── SonarQube ─────────────────────────────────────────── + - name: SonarQube Scan + uses: sonarsource/sonarqube-scan-action@v5 + env: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + SONAR_HOST_URL: https://sn.esy-web.dev + + # ─── Upload reports ────────────────────────────────────── + - name: Upload CI reports + uses: actions/upload-artifact@v3 + if: always() + with: + name: ci-reports + retention-days: 30 + path: | + coverage.xml + phpstan-report.json + eslint-report.json + semgrep-report.json + infection-report.json + hadolint-dev.json + hadolint-prod.json + dc-report.* + phpmetrics-report/ + diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml new file mode 100644 index 0000000..0a65356 --- /dev/null +++ b/.gitea/workflows/deploy.yml @@ -0,0 +1,20 @@ +name: Deploy to production + +on: + workflow_dispatch: + schedule: + - cron: '0 1,22 * * *' + +jobs: + deploy: + runs_on: ubuntu-latest + steps: + - name: Setup SSH key + run: | + mkdir -p ~/.ssh + echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_ed25519 + chmod 600 ~/.ssh/id_ed25519 + ssh-keyscan 34.90.187.4 >> ~/.ssh/known_hosts + + - name: Deploy + run: ssh bot@34.90.187.4 "cd /var/www/crm-ecosplay && ansible-playbook ansible/deploy.yml -i ansible/hosts.ini --vault-password-file <(echo '${{ secrets.ANSIBLE_VAULT_PASSWORD }}')" diff --git a/.gitea/workflows/sonarqube.yml b/.gitea/workflows/sonarqube.yml new file mode 100644 index 0000000..ac5ecb4 --- /dev/null +++ b/.gitea/workflows/sonarqube.yml @@ -0,0 +1,116 @@ +name: SonarQube Full Scan + +on: + workflow_dispatch: + +jobs: + scan: + runs_on: ubuntu-latest + services: + database: + image: postgres:16-alpine + env: + POSTGRES_USER: app + POSTGRES_PASSWORD: secret + POSTGRES_DB: crm_ecosplay + options: >- + --health-cmd "pg_isready -U app -d crm_ecosplay" + --health-interval 5s + --health-timeout 5s + --health-retries 5 + redis: + image: redis:7-alpine + options: >- + --health-cmd "redis-cli ping" + --health-interval 5s + --health-timeout 5s + --health-retries 5 + meilisearch: + image: getmeili/meilisearch:latest + env: + MEILI_MASTER_KEY: test + MEILI_ENV: development + env: + DATABASE_URL: "postgresql://app:secret@database:5432/crm_ecosplay?serverVersion=16&charset=utf8" + MESSENGER_TRANSPORT_DSN: "redis://redis:6379/messages" + MAILER_DSN: "null://null" + MEILISEARCH_URL: "http://meilisearch:7700" + MEILISEARCH_API_KEY: "test" + APP_ENV: test + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.4' + extensions: intl, pdo_pgsql, zip, gd, redis, imagick + coverage: xdebug + + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: '22' + + - name: Setup Bun + uses: oven-sh/setup-bun@v1 + + - name: Install PHP dependencies + run: composer install --no-interaction --prefer-dist + + - name: Install JS dependencies + run: bun install + + - name: JS tests with coverage + run: bun run test:coverage + + - name: PHPStan report + run: vendor/bin/phpstan analyse src/ --level=6 --no-progress --error-format=json > phpstan-report.json || true + + - name: Build assets + run: bun run build + + - name: Create test database + run: php bin/console doctrine:database:create --env=test --if-not-exists + + - name: Create database schema + run: php bin/console doctrine:schema:create --env=test + + - name: PHPUnit with coverage + run: vendor/bin/phpunit --coverage-clover coverage.xml + + - name: ESLint report + run: bunx eslint assets/ --ext .js,.ts -f json -o eslint-report.json || true + + - name: Hadolint + run: | + wget -qO hadolint https://github.com/hadolint/hadolint/releases/latest/download/hadolint-Linux-x86_64 + chmod +x hadolint + ./hadolint docker/php/dev/Dockerfile -f json > hadolint-dev.json || true + ./hadolint docker/php/prod/Dockerfile -f json > hadolint-prod.json || true + + - name: OWASP Dependency-Check + uses: dependency-check/Dependency-Check_Action@main + with: + project: 'crm-ecosplay' + path: '.' + format: 'JSON,HTML' + args: > + --scan composer.lock + --scan package.json + --out . + --disableAssembly + continue-on-error: true + + - name: SonarQube Scan + uses: sonarsource/sonarqube-scan-action@v5 + with: + args: > + -Dsonar.qualitygate.wait=true + -Dsonar.scm.forceReloadAll=true + env: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + SONAR_HOST_URL: https://sn.esy-web.dev diff --git a/.gitignore b/.gitignore index 40af00d..abcf981 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ - ###> symfony/framework-bundle ### /.env.local /.env.local.php @@ -14,7 +13,44 @@ /.phpunit.cache/ ###< phpunit/phpunit ### +/public/build/ +/public/uploads/ +/public/.update +docker-compose-prod.yml +docker/pgsql/pgbouncer.ini +docker/pgsql/userlist.txt +node_modules/ +.idea/ +/cert/ +/config/cert/private-key.pem +/public/media/ +/coverage/ +/coverage-clover.xml + +###> friendsofphp/php-cs-fixer ### +/.php-cs-fixer.php +/.php-cs-fixer.cache +###< friendsofphp/php-cs-fixer ### + ###> symfony/asset-mapper ### /public/assets/ /assets/vendor/ ###< symfony/asset-mapper ### + +###> spomky-labs/pwa-bundle ### +/public/sw.js +/public/workbox +/public/idb +/public/pwa +/public/favicon.ico +/public/site.webmanifest +/public/site.*.webmanifest +###< spomky-labs/pwa-bundle ### + +###> liip/imagine-bundle ### +/public/media/cache/ +###< liip/imagine-bundle ### + +###> phpstan/phpstan ### +phpstan.neon +###< phpstan/phpstan ### diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php new file mode 100644 index 0000000..1c883f0 --- /dev/null +++ b/.php-cs-fixer.dist.php @@ -0,0 +1,17 @@ +in(__DIR__) + ->exclude('var') + ->notPath([ + 'config/bundles.php', + 'config/reference.php', + ]) +; + +return (new PhpCsFixer\Config()) + ->setRules([ + '@Symfony' => true, + ]) + ->setFinder($finder) +; diff --git a/AGENT.md b/AGENT.md new file mode 100644 index 0000000..08c7dbb --- /dev/null +++ b/AGENT.md @@ -0,0 +1,31 @@ +# AGENT.md + +## Interdiction generale + +**Aucun agent IA, assistant de code, ou outil automatise n'est autorise a modifier, committer, pousser, supprimer ou alterer de quelque maniere que ce soit les fichiers de ce depot.** + +Cela inclut, sans s'y limiter : + +- La modification de fichiers existants +- La creation de nouveaux fichiers +- La suppression de fichiers +- Les commits et pushs Git +- L'ouverture de pull requests ou d'issues +- La modification de la configuration du projet +- L'execution de commandes destructrices + +## Portee + +Cette interdiction s'applique a tous les agents IA et outils automatises, y compris mais sans s'y limiter : + +- Claude Code / Claude +- GitHub Copilot +- Cursor +- Windsurf +- Devin +- OpenAI Codex +- Tout autre assistant IA ou bot + +## Exceptions + +Aucune exception. Toute intervention IA sur ce depot est strictement interdite sauf autorisation explicite et ecrite du proprietaire du projet. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..b3f43bc --- /dev/null +++ b/Makefile @@ -0,0 +1,229 @@ +.DEFAULT_GOAL := help + +## —— Help ———————————————————————————————————————— +help: ## Affiche la liste des commandes disponibles + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' + +## —— Docker —————————————————————————————————————— +build_dev: ## Build les images Docker pour le dev + docker compose -f docker-compose-dev.yml build + +build_prod: ## Build les images Docker pour la prod + docker compose -f docker-compose-prod.yml build + +start_dev: ## Lance les containers dev + docker compose -f docker-compose-dev.yml up + +start_prod: ## Lance les containers prod en background + docker compose -f docker-compose-prod.yml up -d + +stop_dev: ## Arrete les containers dev + docker compose -f docker-compose-dev.yml down + +purge_dev: ## Arrete et purge les containers dev (volumes inclus) + docker compose -f docker-compose-dev.yml down -v + +stop_prod: ## Arrete les containers prod + docker compose -f docker-compose-prod.yml down + +## —— Git —————————————————————————————————————————— +pull_dev: ## Pull les images Docker dev + docker compose -f docker-compose-dev.yml pull + +pull_prod: ## Pull les images Docker prod + docker compose -f docker-compose-prod.yml pull + +## —— Ansible ————————————————————————————————————— +deploy_prod: ## Deploy en prod via Ansible (demande le vault password) + ansible-playbook ansible/deploy.yml -i ansible/hosts.ini --ask-vault-pass + +## —— Install —————————————————————————————————————— +install_dev: ## Install les dependances dev via Docker + docker compose -f docker-compose-dev.yml exec php composer install + docker compose -f docker-compose-dev.yml exec bun bun install + +install_prod: ## Install les dependances et build les assets pour la prod + docker compose -f docker-compose-prod.yml exec php composer install --no-dev --optimize-autoloader + bun install + bun run build + +## —— Tests ———————————————————————————————————————— +test_db_create: ## Cree la base de donnees de test + docker compose -f docker-compose-dev.yml exec database psql -U app -d crm_ecosplay -tc "SELECT 1 FROM pg_database WHERE datname = 'crm_ecosplay_test'" | grep -q 1 || \ + docker compose -f docker-compose-dev.yml exec database psql -U app -d crm_ecosplay -c "CREATE DATABASE crm_ecosplay_test" + +test_db_schema: ## Cree/met a jour le schema de la base de test + docker compose -f docker-compose-dev.yml exec php php bin/console doctrine:schema:update --force --env=test + +test_db_migrate: ## Execute les migrations sur la base de test + docker compose -f docker-compose-dev.yml exec php php bin/console doctrine:migrations:migrate --no-interaction --env=test + +test_db_reset: ## Supprime et recree la base de test depuis zero + docker compose -f docker-compose-dev.yml exec database psql -U app -d crm_ecosplay -c "DROP DATABASE IF EXISTS crm_ecosplay_test" + docker compose -f docker-compose-dev.yml exec database psql -U app -d crm_ecosplay -c "CREATE DATABASE crm_ecosplay_test" + $(MAKE) test_db_schema + +test_db_setup: ## Setup complet de la base de test (cree si besoin + schema) + $(MAKE) test_db_create + $(MAKE) test_db_schema + +run_test: ## Lance les tests PHP et JS via Docker dev + docker compose -f docker-compose-dev.yml exec php php bin/phpunit + docker compose -f docker-compose-dev.yml exec bun bun run test + +run_test_php: ## Lance uniquement les tests PHP via Docker dev + docker compose -f docker-compose-dev.yml exec php php bin/phpunit + +run_test_js: ## Lance uniquement les tests JS via Docker dev + docker compose -f docker-compose-dev.yml exec bun bun run test + +run_test_coverage_php: ## Lance les tests PHP avec couverture via Docker dev + docker compose -f docker-compose-dev.yml exec php php bin/phpunit --coverage-text --coverage-html=coverage/php + +run_test_coverage_js: ## Lance les tests JS avec couverture via Docker dev + docker compose -f docker-compose-dev.yml exec bun bun run test -- --coverage + +run_test_file: ## Lance un fichier de test PHP specifique (usage: make run_test_file FILE=tests/Service/AuditServiceTest.php) + docker compose -f docker-compose-dev.yml exec php php bin/phpunit $(FILE) + +## —— PWA ————————————————————————————————————————— +pwa_dev: ## Compile les assets PWA en dev via Docker + docker compose -f docker-compose-dev.yml exec php php bin/console pwa:compile + +pwa_prod: ## Compile les assets PWA en prod via Docker + docker compose -f docker-compose-prod.yml exec php php bin/console pwa:compile --env=prod + +## —— Traductions —————————————————————————————————— +trans: ## Traduit les fichiers YAML FR vers EN/ES/DE/IT via LibreTranslate (Docker dev) + docker compose -f docker-compose-dev.yml exec php php bin/console app:translate + docker compose -f docker-compose-dev.yml stop libretranslate + +trans_prod: ## Traduit les fichiers YAML FR vers EN/ES/DE/IT via LibreTranslate (Docker prod) + docker compose -f docker-compose-prod.yml exec -T php php bin/console app:translate --env=prod + docker compose -f docker-compose-prod.yml stop libretranslate + +## —— Symfony —————————————————————————————————————— +entity: ## Creer ou modifier une entite via Docker dev + docker compose -f docker-compose-dev.yml exec php php bin/console make:entity + +## —— Database —————————————————————————————————————— +migration_dev: ## Genere une migration via Docker dev + docker compose -f docker-compose-dev.yml exec php php bin/console make:migration + +migrate_dev: ## Execute les migrations via Docker dev + docker compose -f docker-compose-dev.yml exec php php bin/console doctrine:migrations:migrate --no-interaction + +force_sql_dev: ## Force la mise a jour du schema de la base de donnees dev + docker compose -f docker-compose-dev.yml exec php php bin/console doctrine:schema:update --force + +migrate_prod: ## Execute les migrations en prod via Docker + docker compose -f docker-compose-prod.yml exec php php bin/console doctrine:migrations:migrate --no-interaction --env=prod + +## —— Cache ———————————————————————————————————————— +clear_prod: ## Clear le cache Symfony et le pool opcache en prod via Docker + docker compose -f docker-compose-prod.yml exec php php bin/console cache:clear --env=prod + docker compose -f docker-compose-prod.yml exec php php bin/console cache:pool:clear --all --env=prod + +purge_liip_dev: ## Purge le cache Liip Imagine en dev via Docker + docker compose -f docker-compose-dev.yml exec php php bin/console liip:imagine:cache:remove + +purge_liip_prod: ## Purge le cache Liip Imagine en prod via Docker + docker compose -f docker-compose-prod.yml exec php php bin/console liip:imagine:cache:remove --env=prod + +## —— Maintenance —————————————————————————————————— +maintenance_on: ## Active le mode maintenance + touch public/.update + +maintenance_off: ## Desactive le mode maintenance + rm -f public/.update + + +## —— Qualite ────────────────────────────────────── +qa: lint phpstan cs_check semgrep ## Lance tous les checks qualite (lint + analyse + sécurité) + +qa_full: qa test_coverage infection phpmetrics ## Lance la suite complète (qa + tests coverage + mutation + metrics) + +lint: ## Lance tous les linters (Twig, YAML, Container, ESLint, Stylelint) + docker compose -f docker-compose-dev.yml exec php php bin/console lint:twig templates/ + docker compose -f docker-compose-dev.yml exec php php bin/console lint:yaml config/ + docker compose -f docker-compose-dev.yml exec php php bin/console lint:container + docker compose -f docker-compose-dev.yml exec bun bunx eslint assets/ --ext .js,.ts + docker compose -f docker-compose-dev.yml exec bun bunx stylelint "assets/**/*.scss" + +phpstan: ## Lance PHPStan niveau 6 + docker compose -f docker-compose-dev.yml exec php vendor/bin/phpstan analyse src/ --level=6 --memory-limit=512M + +phpstan_report: ## Lance PHPStan et genere le rapport JSON pour SonarQube + docker compose -f docker-compose-dev.yml exec php sh -c 'mkdir -p var/reports && vendor/bin/phpstan analyse src/ --level=6 --memory-limit=512M --no-progress --error-format=json > var/reports/phpstan-report.json || true' + +cs_check: ## Verifie le code style (dry-run) + docker compose -f docker-compose-dev.yml exec php vendor/bin/php-cs-fixer fix --dry-run --diff + +cs_fix: ## Corrige automatiquement le code style + docker compose -f docker-compose-dev.yml exec php vendor/bin/php-cs-fixer fix + +eslint: ## Lance ESLint sur les assets JS/TS + docker compose -f docker-compose-dev.yml exec bun bunx eslint assets/ --ext .js,.ts + +eslint_fix: ## Corrige automatiquement les erreurs ESLint + docker compose -f docker-compose-dev.yml exec bun bunx eslint assets/ --ext .js,.ts --fix + +eslint_report: ## Lance ESLint et genere le rapport JSON pour SonarQube + docker compose -f docker-compose-dev.yml exec bun sh -c 'mkdir -p var/reports && bunx eslint assets/ --ext .js,.ts -f json -o var/reports/eslint-report.json || true' + +stylelint: ## Lance Stylelint sur les fichiers SCSS + docker compose -f docker-compose-dev.yml exec bun bunx stylelint "assets/**/*.scss" + +stylelint_fix: ## Corrige automatiquement les erreurs Stylelint + docker compose -f docker-compose-dev.yml exec bun bunx stylelint "assets/**/*.scss" --fix + +semgrep: ## Lance Semgrep SAST sur src/ + docker compose -f docker-compose-dev.yml exec php sh -c 'pip install semgrep --break-system-packages -q && semgrep scan --config=auto src/' + +semgrep_report: + mkdir -p var/reports + # 1. On garde ton scan Docker (il marche très bien) + docker run --rm \ + --user $(shell id -u):$(shell id -g) \ + -v "$(PWD):/src" \ + returntocorp/semgrep \ + semgrep scan --config=auto --json -o var/reports/semgrep-raw.json src/ || true + + # 2. Conversion JQ simplifiée (on ne garde QUE la ligne, pas la colonne) + cat var/reports/semgrep-raw.json | jq '{issues: [.results[] | {engineId: "semgrep", ruleId: .check_id, severity: (if .extra.severity == "ERROR" then "CRITICAL" else "MAJOR" end), type: "VULNERABILITY", primaryLocation: {message: .extra.message, filePath: .path, textRange: {startLine: .start.line}}}]}' > var/reports/semgrep-report.json +test_coverage: ## Lance les tests PHP avec couverture (clover + HTML + JUnit) + docker compose -f docker-compose-dev.yml exec php sh -c 'mkdir -p var/reports && php bin/phpunit --testdox --log-junit var/reports/phpunit.xml --coverage-clover var/reports/coverage.xml --coverage-html var/reports/coverage-html --coverage-text' + +infection: ## Lance Infection (mutation testing) sur les tests PHP + docker compose -f docker-compose-dev.yml exec php sh -c 'mkdir -p var/reports && vendor/bin/infection --threads=4 --min-msi=50 --min-covered-msi=60 --logger-json=var/reports/infection-report.json || true' + +infection_full: test_coverage infection ## Lance les tests avec couverture puis Infection + +phpmetrics: ## Genere le rapport PHPMetrics HTML + docker compose -f docker-compose-dev.yml exec php sh -c 'mkdir -p var/reports && curl -sLo /tmp/phpmetrics.phar https://github.com/phpmetrics/PhpMetrics/releases/latest/download/phpmetrics.phar && php /tmp/phpmetrics.phar --report-html=var/reports/phpmetrics-report src/' + +hadolint: ## Lance Hadolint sur les Dockerfiles + docker run --rm -v "$(PWD):/app" -w /app hadolint/hadolint hadolint docker/php/dev/Dockerfile + docker run --rm -v "$(PWD):/app" -w /app hadolint/hadolint hadolint docker/php/prod/Dockerfile + +hadolint_report: ## Lance Hadolint sur le Dockerfile prod et genere le rapport JSON + mkdir -p var/reports + # Use -i (interactive) and ensure no TTY is allocated to avoid escape characters + docker run --rm -v "$(PWD):/app" -w /app hadolint/hadolint \ + hadolint docker/php/prod/Dockerfile -f json > var/reports/hadolint-raw.json || true + + # Cleanup: Ensure the file isn't empty and contains valid JSON brackets + @if [ ! -s var/reports/hadolint-raw.json ]; then echo "[]" > var/reports/hadolint-raw.json; fi + # Optional: Strip potential Docker/System noise from the file + @sed -i 's/^[^{|[ ]*//g' var/reports/hadolint-raw.json +audit: ## Lance l'audit de securite Composer + docker compose -f docker-compose-dev.yml exec php composer audit + +reports: phpstan_report eslint_report semgrep_report test_coverage hadolint_report phpmetrics ## Genere tous les rapports pour SonarQube + +## —— SonarQube ———————————————————————————————————— +sonar: reports ## Genere les rapports puis lance le scan SonarQube + docker run --rm -v "$(PWD):/usr/src" -e SONAR_HOST_URL=https://sn.esy-web.dev -e SONAR_TOKEN=$(shell grep SONAR_TOKEN .env.local 2>/dev/null | cut -d= -f2 || echo "") sonarsource/sonar-scanner-cli + +sonar_quick: ## Lance le scan SonarQube sans regenerer les rapports + docker run --rm -v "$(PWD):/usr/src" -e SONAR_HOST_URL=https://sn.esy-web.dev -e SONAR_TOKEN=$(shell grep SONAR_TOKEN .env.local 2>/dev/null | cut -d= -f2 || echo "") sonarsource/sonar-scanner-cli diff --git a/ansible/.gitkeep b/ansible/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/ansible/backup.sh.j2 b/ansible/backup.sh.j2 new file mode 100644 index 0000000..59969a7 --- /dev/null +++ b/ansible/backup.sh.j2 @@ -0,0 +1,34 @@ +#!/bin/bash +# CRM Ecosplay database backup script +# Runs every 30 minutes via cron + +BACKUP_DIR="/var/backups/crm-ecosplay" +DATE=$(date +%Y%m%d_%H%M%S) +FILENAME="crm_ecosplay_${DATE}.sql.gz" +KEEP_DAYS=1 + +# Dump database via Docker +docker compose -f /var/www/crm-ecosplay/docker-compose-prod.yml exec -T db-master pg_dump -U {{ db_user | default('crm-ecosplay') }} {{ db_name | default('crm-ecosplay') }} | gzip > "${BACKUP_DIR}/${FILENAME}" + +# Check if backup was created +if [ -f "${BACKUP_DIR}/${FILENAME}" ] && [ -s "${BACKUP_DIR}/${FILENAME}" ]; then + echo "[$(date)] DB Backup OK: ${FILENAME} ($(du -h "${BACKUP_DIR}/${FILENAME}" | cut -f1))" +else + echo "[$(date)] ERROR: DB Backup failed" + exit 1 +fi + +# Backup uploads +UPLOADS_FILENAME="uploads_${DATE}.tar.gz" +tar -czf "${BACKUP_DIR}/${UPLOADS_FILENAME}" -C /var/www/crm-ecosplay/public uploads 2>/dev/null + +if [ -f "${BACKUP_DIR}/${UPLOADS_FILENAME}" ]; then + echo "[$(date)] Uploads Backup OK: ${UPLOADS_FILENAME} ($(du -h "${BACKUP_DIR}/${UPLOADS_FILENAME}" | cut -f1))" +else + echo "[$(date)] WARNING: Uploads backup failed" +fi + +# Remove backups older than KEEP_DAYS days +find "${BACKUP_DIR}" -name "crm_ecosplay_*.sql.gz" -mtime +${KEEP_DAYS} -delete +find "${BACKUP_DIR}" -name "uploads_*.tar.gz" -mtime +${KEEP_DAYS} -delete +echo "[$(date)] Cleaned backups older than ${KEEP_DAYS} days" diff --git a/ansible/caddy.j2 b/ansible/caddy.j2 new file mode 100644 index 0000000..3897c0c --- /dev/null +++ b/ansible/caddy.j2 @@ -0,0 +1,53 @@ +crm.e-cosplay.fr { + tls { + dns cloudflare cfat_rIHZqzCm9GKK3xVnQDNGfu6J91TseIDdTKeuWSFUdf6ccd31 + } + + @static path /logo.png /favicon.ico /favicon.png /marker.png /screen.png /manifest.json /site.webmanifest /sw.js /unavailable.html /workbox/* /idb/* /build/* /uploads/* /pwa/* + handle @static { + root * /var/www/crm-crm-ecosplay/public + file_server + } + + @maintenance file /var/www/crm-ecosplay/public/.update + handle @maintenance { + root * /var/www/crm-ecosplay/public + rewrite * /maintenance.html + file_server { + status 503 + } + } + + handle { + root * /var/www/crm-ecosplay/public + php_fastcgi localhost:4568 localhost:4569 { + root /app/public + lb_policy round_robin + trusted_proxies private_ranges + dial_timeout 5s + read_timeout 30s + write_timeout 30s + } + file_server + try_files {path} /index.php?{query} + } + + handle_errors { + @unavailable expression `{err.status_code} in [502, 503]` + handle @unavailable { + root * /var/www/crm-ecosplay/public + rewrite * /unavailable.html + file_server + } + } + + encode gzip + + header { + -Server + } + + log { + output file /var/log/caddy/ticket.e-cosplay.fr.log + } +} diff --git a/ansible/deploy.yml b/ansible/deploy.yml new file mode 100644 index 0000000..67a0fdf --- /dev/null +++ b/ansible/deploy.yml @@ -0,0 +1,278 @@ +--- +# --- Server deployment --- +- name: Deploy crm-ecosplay to production + hosts: production + become: true + vars_files: + - vault.yml + + pre_tasks: + - name: Enable maintenance mode + command: make maintenance_on + args: + chdir: /var/www/crm-ecosplay + + - name: Get Docker socket GID + stat: + path: /var/run/docker.sock + register: docker_sock + + - name: Set docker_gid fact + set_fact: + docker_gid: "{{ docker_sock.stat.gid }}" + + - name: Generate analytics secret + set_fact: + analytics_secret: "{{ lookup('password', '/dev/null chars=ascii_lowercase,digits length=32') }}" + + tasks: + - name: Deploy .env.local + template: + src: env.local.j2 + dest: /var/www/crm-ecosplay/.env.local + owner: bot + group: bot + mode: "0600" + + - name: Ensure cert directory exists + file: + path: /var/www/crm-ecosplay/config/cert + state: directory + owner: bot + group: bot + mode: "0700" + + - name: Deploy S/MIME private key + copy: + content: "{{ smime_private_key }}" + dest: /var/www/crm-ecosplay/config/cert/private-key.pem + owner: bot + group: bot + mode: "0600" + + - name: Pull latest code + command: git pull origin master + args: + chdir: /var/www/crm-ecosplay + become_user: bot + + - name: Deploy PgBouncer config + template: + src: pgbouncer.ini.j2 + dest: /var/www/crm-ecosplay/docker/pgsql/pgbouncer.ini + owner: bot + group: bot + mode: "0644" + + - name: Deploy PgBouncer userlist + template: + src: userlist.txt.j2 + dest: /var/www/crm-ecosplay/docker/pgsql/userlist.txt + owner: bot + group: bot + mode: "0644" + + - name: Deploy docker-compose-prod.yml + template: + src: docker-compose-prod.yml.j2 + dest: /var/www/crm-ecosplay/docker-compose-prod.yml + owner: bot + group: bot + mode: "0600" + + - name: Build Docker images + command: make build_prod + args: + chdir: /var/www/crm-ecosplay + + - name: Pull Docker images + command: make pull_prod + args: + chdir: /var/www/crm-ecosplay + + - name: Stop production containers + command: make stop_prod + args: + chdir: /var/www/crm-ecosplay + + - name: Start production containers + command: make start_prod + args: + chdir: /var/www/crm-ecosplay + + - name: Install PHP dependencies + command: composer install --no-dev --optimize-autoloader + args: + chdir: /var/www/crm-ecosplay + become_user: bot + + - name: Install JS dependencies + command: bun install + args: + chdir: /var/www/crm-ecosplay + become_user: bot + + - name: Build assets + command: bun run build + args: + chdir: /var/www/crm-ecosplay + become_user: bot + + - name: Wait for database to be ready + shell: | + for i in $(seq 1 30); do + docker compose -f docker-compose-prod.yml exec -T php php -r "new PDO('pgsql:host=pgbouncer;port=6432;dbname=crm-ecosplay','crm-ecosplay','{{ db_password }}');" 2>/dev/null && exit 0 + sleep 1 + done + exit 1 + args: + chdir: /var/www/crm-ecosplay + + - name: Clear Symfony cache before migration + command: docker compose -f docker-compose-prod.yml exec -T php php bin/console cache:clear --env=prod + args: + chdir: /var/www/crm-ecosplay + + - name: Clear Redis cache pool (Doctrine L2 + app cache) + command: docker compose -f docker-compose-prod.yml exec -T php php bin/console cache:pool:clear cache.app --env=prod + args: + chdir: /var/www/crm-ecosplay + + - name: Run migrations + command: make migrate_prod + args: + chdir: /var/www/crm-ecosplay + + - name: Clear cache after migration + command: make clear_prod + args: + chdir: /var/www/crm-ecosplay + + - name: Compile PWA assets + command: make pwa_prod + args: + chdir: /var/www/crm-ecosplay + + - name: Ensure uploads directories exist with correct permissions + file: + path: "/var/www/crm-ecosplay/public/uploads/{{ item }}" + state: directory + owner: "1000" + group: "1000" + mode: "0755" + recurse: true + loop: + - logos + + - name: Ensure var/payouts directory exists + file: + path: /var/www/crm-ecosplay/var/payouts + state: directory + owner: "1000" + group: "1000" + mode: "0755" + + - name: Ensure Caddy sites directory exists + file: + path: /etc/caddy/sites + state: directory + owner: root + group: root + mode: "0755" + + - name: Deploy Caddy config + template: + src: caddy.j2 + dest: /etc/caddy/sites/crm-ecosplay.conf + owner: root + group: root + mode: "0644" + notify: Reload Caddy + + - name: Create backup directory + file: + path: /var/backups/crm-ecosplay + state: directory + owner: bot + group: bot + mode: "0750" + + - name: Deploy backup script + template: + src: backup.sh.j2 + dest: /var/backups/crm-ecosplay/backup.sh + owner: bot + group: bot + mode: "0750" + + - name: Configure backup cron (every 30 minutes) + cron: + name: "crm-ecosplay database backup" + minute: "*/30" + job: "/var/backups/crm-ecosplay/backup.sh >> /var/log/crm-ecosplay-backup.log 2>&1" + user: bot + + - name: Configure expire pending orders cron (every 5 minutes) + cron: + name: "crm-ecosplay expire pending orders" + minute: "*/5" + job: "docker compose -f /var/www/crm-ecosplay/docker-compose-prod.yml exec -T php php bin/console app:orders:expire-pending --env=prod >> /var/log/crm-ecosplay-expire-orders.log 2>&1" + user: bot + + - name: Configure messenger monitor cron (every hour) + cron: + name: "crm-ecosplay messenger monitor" + minute: "0" + job: "docker compose -f /var/www/crm-ecosplay/docker-compose-prod.yml exec -T php php bin/console app:monitor:messenger --env=prod >> /var/log/crm-ecosplay-messenger.log 2>&1" + user: bot + + - name: Configure Meilisearch consistency check cron (daily at 3am) + cron: + name: "crm-ecosplay meilisearch consistency" + minute: "0" + hour: "3" + job: "docker compose -f /var/www/crm-ecosplay/docker-compose-prod.yml exec -T php php bin/console app:meilisearch:check-consistency --fix --env=prod >> /var/log/crm-ecosplay-meilisearch.log 2>&1" + user: bot + + - name: Configure Stripe sync cron (every 6 hours) + cron: + name: "crm-ecosplay stripe sync" + minute: "0" + hour: "*/6" + job: "docker compose -f /var/www/crm-ecosplay/docker-compose-prod.yml exec -T php php bin/console app:stripe:sync --env=prod >> /var/log/crm-ecosplay-stripe-sync.log 2>&1" + user: bot + + - name: Configure infra snapshot cron (every 5 minutes) + cron: + name: "crm-ecosplay infra snapshot" + minute: "*/5" + job: "docker compose -f /var/www/crm-ecosplay/docker-compose-prod.yml exec -T php php bin/console app:infra:snapshot --env=prod >> /var/log/crm-ecosplay-infra.log 2>&1" + user: bot + + - name: Configure attestations cleanup cron (daily at 4am) + cron: + name: "crm-ecosplay attestations clean" + minute: "0" + hour: "4" + job: "docker compose -f /var/www/crm-ecosplay/docker-compose-prod.yml exec -T php php bin/console app:attestations:clean --env=prod >> /var/log/crm-ecosplay-attestations.log 2>&1" + user: bot + + - name: Configure services health check cron (every 15 minutes) + cron: + name: "crm-ecosplay services check" + minute: "*/15" + job: "docker compose -f /var/www/crm-ecosplay/docker-compose-prod.yml exec -T php php bin/console app:services:check --env=prod >> /var/log/crm-ecosplay-services.log 2>&1" + user: bot + + post_tasks: + - name: Disable maintenance mode + command: make maintenance_off + args: + chdir: /var/www/crm-ecosplay + + handlers: + - name: Reload Caddy + systemd: + name: caddy + state: reloaded + diff --git a/ansible/docker-compose-prod.yml.j2 b/ansible/docker-compose-prod.yml.j2 new file mode 100644 index 0000000..898e2ce --- /dev/null +++ b/ansible/docker-compose-prod.yml.j2 @@ -0,0 +1,211 @@ +name: crm-ecosplay + +services: + php: + build: + context: ./docker/php/prod + dockerfile: Dockerfile + deploy: + replicas: 2 + resources: + limits: + cpus: "1.5" + memory: 1G + reservations: + cpus: "0.5" + memory: 256M + restart: unless-stopped + group_add: + - "{{ docker_gid }}" + volumes: + - .:/app + - /var/run/docker.sock:/var/run/docker.sock:ro + ports: + - "4568-4569:9000" + networks: + - crm_ecosplay + depends_on: + pgbouncer: + condition: service_healthy + redis: + condition: service_healthy + + db-master: + image: postgres:16-alpine + restart: unless-stopped + deploy: + resources: + limits: + cpus: "2.0" + memory: 4G + reservations: + cpus: "0.5" + memory: 1G + environment: + POSTGRES_USER: crm-ecosplay + POSTGRES_PASSWORD: {{ db_password }} + POSTGRES_DB: crm-ecosplay + command: + - postgres + - -c + - wal_level=replica + - -c + - max_wal_senders=3 + - -c + - wal_keep_size=64MB + - -c + - hot_standby=on + - -c + - shared_buffers=1GB + - -c + - effective_cache_size=3GB + - -c + - work_mem=16MB + volumes: + - db-master-data:/var/lib/postgresql/data + - ./docker/pgsql/init-master.sh:/docker-entrypoint-initdb.d/init-master.sh + networks: + - crm_ecosplay + healthcheck: + test: ["CMD-SHELL", "pg_isready -U crm-ecosplay -d crm-ecosplay"] + interval: 5s + timeout: 5s + retries: 5 + + db-slave: + image: postgres:16-alpine + restart: unless-stopped + deploy: + resources: + limits: + cpus: "1.5" + memory: 2G + reservations: + cpus: "0.25" + memory: 512M + environment: + POSTGRES_USER: crm-ecosplay + POSTGRES_PASSWORD: {{ db_password }} + POSTGRES_DB: crm-ecosplay + PGDATA: /var/lib/postgresql/data + volumes: + - db-slave-data:/var/lib/postgresql/data + - ./docker/pgsql/init-slave.sh:/init-slave.sh + entrypoint: ["/bin/bash", "/init-slave.sh"] + command: ["postgres"] + networks: + - crm_ecosplay + depends_on: + db-master: + condition: service_healthy + healthcheck: + test: ["CMD-SHELL", "pg_isready -U crm-ecosplay -d crm-ecosplay"] + interval: 5s + timeout: 5s + retries: 5 + + pgbouncer: + image: edoburu/pgbouncer + restart: unless-stopped + deploy: + resources: + limits: + cpus: "0.5" + memory: 128M + reservations: + cpus: "0.1" + memory: 32M + volumes: + - ./docker/pgsql/pgbouncer.ini:/etc/pgbouncer/pgbouncer.ini:ro + - ./docker/pgsql/userlist.txt:/etc/pgbouncer/userlist.txt:ro + networks: + - crm_ecosplay + depends_on: + db-master: + condition: service_healthy + db-slave: + condition: service_healthy + healthcheck: + test: ["CMD-SHELL", "pg_isready -h 127.0.0.1 -p 6432 -U crm-ecosplay"] + interval: 5s + timeout: 5s + retries: 5 + + messenger: + build: + context: ./docker/php/prod + dockerfile: Dockerfile + command: php bin/console messenger:consume async --time-limit=3600 --memory-limit=256M --limit=500 -vv + deploy: + replicas: 2 + resources: + limits: + cpus: "1.0" + memory: 512M + reservations: + cpus: "0.25" + memory: 128M + restart: unless-stopped + volumes: + - .:/app + networks: + - crm_ecosplay + depends_on: + pgbouncer: + condition: service_healthy + redis: + condition: service_healthy + meilisearch: + condition: service_started + + redis: + image: redis:7-alpine + restart: unless-stopped + deploy: + resources: + limits: + cpus: "1.0" + memory: 1G + reservations: + cpus: "0.25" + memory: 128M + command: redis-server --requirepass {{ redis_password }} --maxmemory 768mb --maxmemory-policy allkeys-lru + volumes: + - redis-data:/data + networks: + - crm_ecosplay + healthcheck: + test: ["CMD", "redis-cli", "-a", "{{ redis_password }}", "ping"] + interval: 5s + timeout: 5s + retries: 5 + + meilisearch: + image: getmeili/meilisearch:latest + restart: unless-stopped + deploy: + resources: + limits: + cpus: "1.0" + memory: 1G + reservations: + cpus: "0.25" + memory: 256M + environment: + MEILI_MASTER_KEY: {{ meilisearch_master_key }} + MEILI_ENV: production + MEILI_NO_ANALYTICS: true + volumes: + - meilisearch-data:/meili_data + networks: + - crm_ecosplay + +networks: + crm_ecosplay: + driver: bridge + +volumes: + db-master-data: + db-slave-data: + redis-data: + meilisearch-data: diff --git a/ansible/env.local.j2 b/ansible/env.local.j2 new file mode 100644 index 0000000..77783c6 --- /dev/null +++ b/ansible/env.local.j2 @@ -0,0 +1,33 @@ +APP_ENV=prod +APP_SECRET={{ app_secret }} +DATABASE_URL="postgresql://crm-ecosplay:{{ db_password }}@pgbouncer:6432/crm-ecosplay?serverVersion=16&charset=utf8" +MESSENGER_TRANSPORT_DSN=redis://:{{ redis_password }}@redis:6379/messages +SESSION_HANDLER_DSN=redis://:{{ redis_password }}@redis:6379/1 +REDIS_CACHE_DSN=redis://:{{ redis_password }}@redis:6379/2 +MAILER_DSN={{ mailer_dsn }} +DEFAULT_URI=https://ticket.e-cosplay.fr +VITE_LOAD=1 +REAL_MAIL=1 +OUTSIDE_URL=https://ticket.e-cosplay.fr +STRIPE_PK={{ stripe_pk }} +STRIPE_SK={{ stripe_sk }} +STRIPE_WEBHOOK_SECRET={{ stripe_webhook_secret }} +STRIPE_WEBHOOK_SECRET_CONNECT={{ stripe_webhook_secret_connect }} +STRIPE_MODE=live +SMIME_PASSPHRASE='{{ smime_passphrase }}' +MEILISEARCH_URL=http://meilisearch:7700 +MEILISEARCH_API_KEY={{ meilisearch_api_key }} +SONARQUBE_URL=https://sn.esy-web.dev +SONARQUBE_BADGE_TOKEN={{ sonarqube_badge_token }} +SONARQUBE_PROJECT_KEY=crm-ecosplay +OAUTH_KEYCLOAK_CLIENT_ID=crm_ecosplay +OAUTH_KEYCLOAK_CLIENT_SECRET=QiksEpHqDCHFPMM9CWb3RHfag31VJfIV +OAUTH_KEYCLOAK_URL=https://auth.esy-web.dev +OAUTH_KEYCLOAK_REALM=e-cosplay +SECRET_ANALYTICS={{ analytics_secret }} +KEYCLOAK_ADMIN_CLIENT_ID=crm-ecosplay-admin +KEYCLOAK_ADMIN_CLIENT_SECRET={{ keycloak_admin_client_secret }} +DOCUSEAL_URL=https://signature.esy-web.dev +DOCUSEAL_API={{ docuseal_api }} +DOCUSEAL_WEBHOOKS_SECRET_HEADER=X-Sign +DOCUSEAL_WEBHOOKS_SECRET={{ docuseal_webhooks_secret }} diff --git a/ansible/hosts.ini b/ansible/hosts.ini new file mode 100644 index 0000000..937139e --- /dev/null +++ b/ansible/hosts.ini @@ -0,0 +1,5 @@ +[production] +127.0.0.1 ansible_user=bot ansible_become=yes ansible_become_method=sudo ansible_connection=local + +[production:vars] +deploy_path=/var/www/crm.ecosplay/ diff --git a/ansible/messenger.j2 b/ansible/messenger.j2 new file mode 100644 index 0000000..5b218b5 --- /dev/null +++ b/ansible/messenger.j2 @@ -0,0 +1,12 @@ +[program:crm-ecosplay-messenger] +command=docker compose -f /var/www/crm-ecosplay/docker-compose-prod.yml exec php php bin/console messenger:consume async --time-limit=3600 --memory-limit=256M --limit=500 +directory=/var/www/crm-ecosplay +user=bot +numprocs=2 +process_name=%(program_name)s_%(process_num)02d +autostart=true +autorestart=true +startsecs=0 +startretries=10 +stdout_logfile=/var/log/supervisor/crm-ecosplay-messenger-%(process_num)02d.log +stderr_logfile=/var/log/supervisor/crm-ecosplay-messenger-%(process_num)02d-error.log diff --git a/ansible/pgbouncer.ini.j2 b/ansible/pgbouncer.ini.j2 new file mode 100644 index 0000000..9364b48 --- /dev/null +++ b/ansible/pgbouncer.ini.j2 @@ -0,0 +1,21 @@ +[databases] +crm-ecosplay = host=db-master port=5432 dbname=crm-ecosplay +crm-ecosplay_readonly = host=db-slave port=5432 dbname=crm-ecosplay + +[pgbouncer] +listen_addr = 0.0.0.0 +listen_port = 6432 +auth_type = plain +auth_file = /etc/pgbouncer/userlist.txt +pool_mode = transaction +max_client_conn = 200 +default_pool_size = 20 +min_pool_size = 5 +reserve_pool_size = 5 +reserve_pool_timeout = 3 +server_lifetime = 3600 +server_idle_timeout = 600 +log_connections = 0 +log_disconnections = 0 +admin_users = crm-ecosplay +stats_users = crm-ecosplay diff --git a/ansible/userlist.txt.j2 b/ansible/userlist.txt.j2 new file mode 100644 index 0000000..a878a4b --- /dev/null +++ b/ansible/userlist.txt.j2 @@ -0,0 +1 @@ +"crm-ecosplay" "{{ db_password }}" diff --git a/ansible/vault.yml b/ansible/vault.yml new file mode 100644 index 0000000..82b40f2 --- /dev/null +++ b/ansible/vault.yml @@ -0,0 +1,50 @@ +cloudflare_api_token: cfk_wXHshV0AmOoKbWE19CIPoWJBeUCtSDUXo2mgkGuNe99eeac8 +cloudflare_zone_id: f9e2a44aa512b455c99286d2ae845862 +app_secret: 533326ee6417086112c42b1fc1843ff1db82b881988f87442118178ea4469cc2 +mailer_dsn: ses+smtp://AKIAWTT2T22CWBRBBDYN:BBdgb6KxRQ8mNcpWFJsZCJxbSGNdgLhKFiITMErfBlQP@default?region=eu-west-3&timeout=120 +stripe_pk: pk_live_51SUA1rP4ub49xK2ThoRH8efqGYNi1hrcWMzrqmDtJpMv12cmTzLa8ncJLUKLbOQNZTkm1jgptLfwt4hxEGqkVsHB00AK3ieZNl +stripe_sk: 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 +stripe_webhook_secret: whsec_ivqY8C05aJ9mho2N3opJLriO2AuxIqOi +stripe_webhook_secret_connect: whsec_0sG9BPYDysxQst9W6FTp44GpwtO2F6WQ +smime_passphrase: 'EVz5zNV8h4ndSLOCWO9JeaQnIertQm7k' +meilisearch_api_key: b9b0994ee46d9826a4ac58306c171fdd00c03ac6f8ff6a68cdd8f9f977ab415a +meilisearch_master_key: 9e6a86cb9e9b09cdabf30a81e43bdb18892ee0d0ab478a16c50749b8f2ed83e4 +db_password: 46eafec68e1e7bc8015790998a2e8ea8b5e31461479588b7 +redis_password: 51f7559d1d14a6cf422628537fa562a94481936228e9291d +sonarqube_badge_token: sqb_dc1d0f73af1016295f49d1c56bf60e115e43bf48 +keycloak_admin_client_secret: QqYnQc6p9aio4sBJYKNhpPsdrvpUtt2z +docuseal_api: pgAU116mCFmeF7WQSezHqxtZW8V1fgo31u5d2FXoaKe +docuseal_webhooks_secret: CRM_COSLAY +smime_private_key: | + Bag Attributes + localKeyID: 75 15 E3 C2 1D 7B 61 75 99 B9 22 D8 FD A4 19 AC 6B BE 1F 8F + friendlyName: contact@e-cosplay.fr + Key Attributes: + -----BEGIN PRIVATE KEY----- + MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC60+PtobUKQsjH + vrRizgwIfR06OkOqYosn17wurYUCZquhGfaRL5nf+qDcOF2liczRfXOsTxnEK0J0 + s38JF9zWXTkIms4yfyL9xKOCBEPkNSe03wIcaKQjNB1iuDqW6npUm/ke6VmIAj4q + ig6wwiViqWxkzcE2824RFBI0H4rtkxDNhEXOk6ccVzxz/MwxAvmKHuJQG7LtbysI + KIZcGbNjlm2YmIebVYU44kpI0ZMzfvKsfy9eWX3VyYubpL4Rwsc9I+Z2jsun+nEc + 4heHamNu8NrKHWQP802NZvRkMR7t39APYnGcEOBe6eagDkn9DQpZrkxfXYUcteBA + 5y3RVBxJAgMBAAECggEASCuXNlJL0blnv0WLgEmRO2w9cKNWRUkI8Wy0rKFqKrnH + nhWd+orVWcEDB98cSdi4Dsrz9sGdtfpyEwvNzJDzxblAUXMfqUEUnswpz41fPpZM + DuJA+gFwOoskuOBq/aZ5eBrCeu7wn4oiQqOPNjfhYkIJdc1H0Lbrs1w6BfmTXN9S + ilSL4GMv7YpEBPt86wiReIbE0DIqaR9Vm1UupkJe7WMc9LrxW6OM4kD5xQTFdJLC + n6QbbX+TE3q+d6TrckK8PeBm+gIX2+NdEuuz7gpZGSybfqN1wqbkziUsifi+7gQi + pxwxhDmCQ08DHplcBdjg1ZldVhaAdHEqCyWZtIZQGwKBgQD9RNXOskPhGBP28oZz + a7APhROKHcEtb9BRj3mwzwIMr3SzJjoBI4SfRR3QcU8jD2uCLNio/cSqjAJVyk2f + Z9wDONnXy4aNrsze0Fx+0t1VqIOVocFDJJHndHCiq2HHorqArvHtZxkj/0xIUx+K + nVltevm7qLSAtR/vWTM1c9Zp/wKBgQC816PZxNEpyg5mgO+3gn5yONsObh2ZyZ/v + KPEmFBjl69AfAxKeaEZaj35RmzdnAFKlv9QzNdAM67+oOIThcaQ+diyNUQegaJwc + amUZHWjQ7RAJNM7lQz85zn9s/MZVC8iNF7xlPWK8Y/ifMwk5e+pAQ79PVwxKiW8y + doXv1uuptwKBgQCPfVJNGr5OZx/YhysZdWd4Q4MAez8ZCQJTUyA0xfN5UGajoQK3 + 5nGa8sOjdq7JRFE6nUHa7HthzJT+GfHEYElMIgd5tTt0RhaYHiBpUfpIL4LtYYln + Cu8G3Pd7kfGB83YKthMm14Rs2pVTXJTAKcmit0uJwFkTmoqPRlA29nwB7QKBgQCp + iVtzxcwWnW1iPz13XfNRbpPTl66Bg89PMk2VZxOXT6cQQHz1tB39bbf1YclScqrA + XcEPnK9E+l2dk+hQUesnnIaQ9H9JVsFYjXhoHMweTcZIzZ01nLLlnesumJ0ieyq4 + 6OddaMf85C+wFWyhrAr5WRknDLpfLJWlE11ZnnUCbwKBgQCXOLstj7bEi8N3H8PV + UQlkFKrAmmEE4i3Y3Qqa1DMbm2XJBzPQQJIrDlCeIWs0jk0upOY34W+deWH5gbvW + a/6bpvSzbWqgFD5DnayVkb7CvgEg+CKdUoUumRSs38pet29c/pdKPUxhMq8voIEg + HpnyJcapAiLw0hv4ql380onf0A== + -----END PRIVATE KEY----- diff --git a/assets/app.js b/assets/app.js index e56396c..1d9f667 100644 --- a/assets/app.js +++ b/assets/app.js @@ -1,10 +1,149 @@ -import './stimulus_bootstrap.js'; -/* - * Welcome to your app's main JavaScript file! - * - * This file will be included onto the page via the importmap() Twig function, - * which should already be in your base.html.twig. - */ -import './styles/app.css'; +import "./app.scss" -console.log('This log comes from assets/app.js - welcome to AssetMapper! 🎉'); +// Membre / Super Admin : mutuellement exclusif +document.addEventListener('DOMContentLoaded', () => { + const memberCheckbox = document.querySelector('input[value="gp_member"]'); + const adminCheckbox = document.querySelector('input[value="super_admin_asso"]'); + + if (memberCheckbox && adminCheckbox) { + memberCheckbox.addEventListener('change', () => { + if (memberCheckbox.checked) { + document.querySelectorAll('input[name="groups[]"]').forEach(cb => { + if (cb !== memberCheckbox) { + cb.checked = false; + } + }); + } + }); + + adminCheckbox.addEventListener('change', () => { + if (adminCheckbox.checked) { + memberCheckbox.checked = false; + document.querySelectorAll('input[name="groups[]"]').forEach(cb => { + if (cb !== memberCheckbox) { + cb.checked = true; + } + }); + } + }); + } + + // Stats period selector + const periodSelect = document.getElementById('stats-period-select'); + const customRange = document.getElementById('stats-custom-range'); + if (periodSelect && customRange) { + periodSelect.addEventListener('change', () => { + customRange.classList.toggle('hidden', periodSelect.value !== 'custom'); + }); + } + + // data-confirm + document.querySelectorAll('form[data-confirm]').forEach(form => { + form.addEventListener('submit', (e) => { + if (!confirm(form.dataset.confirm)) { + e.preventDefault(); + } + }); + }); + + // Sidebar dropdown toggle + document.querySelectorAll('.sidebar-dropdown-btn').forEach(btn => { + btn.addEventListener('click', () => { + const menu = btn.nextElementSibling; + const arrow = btn.querySelector('.sidebar-dropdown-arrow'); + menu?.classList.toggle('hidden'); + arrow?.classList.toggle('rotate-180'); + }); + }); + + // Mobile sidebar toggle + const sidebarToggle = document.getElementById('admin-sidebar-toggle'); + const sidebar = document.getElementById('admin-sidebar'); + const overlay = document.getElementById('admin-overlay'); + + if (sidebarToggle && sidebar && overlay) { + sidebarToggle.addEventListener('click', () => { + sidebar.classList.toggle('open'); + }); + + overlay.addEventListener('click', () => { + sidebar.classList.remove('open'); + }); + } + + // Mobile menu toggle (public) + const mobileMenuBtn = document.getElementById('mobile-menu-btn'); + const mobileMenu = document.getElementById('mobile-menu'); + const menuIconOpen = document.getElementById('menu-icon-open'); + const menuIconClose = document.getElementById('menu-icon-close'); + + if (mobileMenuBtn && mobileMenu) { + mobileMenuBtn.addEventListener('click', () => { + mobileMenu.classList.toggle('hidden'); + menuIconOpen?.classList.toggle('hidden'); + menuIconClose?.classList.toggle('hidden'); + }); + } + + // Cookie banner + const cookieBanner = document.getElementById('cookie-banner'); + const cookieAccept = document.getElementById('cookie-accept'); + const cookieRefuse = document.getElementById('cookie-refuse'); + + if (cookieBanner && !localStorage.getItem('cookie_consent')) { + cookieBanner.classList.remove('hidden'); + } + + cookieAccept?.addEventListener('click', () => { + localStorage.setItem('cookie_consent', 'accepted'); + cookieBanner?.classList.add('hidden'); + }); + + cookieRefuse?.addEventListener('click', () => { + localStorage.setItem('cookie_consent', 'refused'); + cookieBanner?.classList.add('hidden'); + }); + + // Search (customers & revendeurs) + const setupSearch = (inputId, resultsId, searchUrl, linkPrefix) => { + const input = document.getElementById(inputId); + const results = document.getElementById(resultsId); + if (!input || !results) return; + + let debounce; + input.addEventListener('input', () => { + clearTimeout(debounce); + const q = input.value.trim(); + if (q.length < 2) { + results.classList.add('hidden'); + results.innerHTML = ''; + return; + } + debounce = setTimeout(async () => { + const resp = await fetch(`${searchUrl}?q=${encodeURIComponent(q)}`); + const hits = await resp.json(); + if (hits.length === 0) { + results.innerHTML = '
Aucun resultat.
'; + } else { + results.innerHTML = hits.map(h => + ` + ${h.fullName || h.raisonSociale || (h.firstName + ' ' + h.lastName)} + ${h.email ? `${h.email}` : ''} + ${h.codeRevendeur ? `${h.codeRevendeur}` : ''} + ` + ).join(''); + } + results.classList.remove('hidden'); + }, 300); + }); + + document.addEventListener('click', (e) => { + if (!results.contains(e.target) && e.target !== input) { + results.classList.add('hidden'); + } + }); + }; + + setupSearch('search-customers', 'search-results', '/admin/clients/search', '/admin/clients/'); + setupSearch('search-revendeurs', 'search-results-revendeurs', '/admin/revendeurs/search', '/admin/revendeurs/'); +}); diff --git a/assets/app.scss b/assets/app.scss new file mode 100644 index 0000000..b20ad8d --- /dev/null +++ b/assets/app.scss @@ -0,0 +1,28 @@ +@import "tailwindcss"; + +.page-container { + width: 90%; + max-width: 1200px; + margin: 0 auto; + padding: 2rem 0; + + @media (min-width: 768px) { + width: 80%; + padding: 3rem 0; + } +} + +.heading-page { + border-bottom: 4px solid #111827; + display: inline-block; + padding-bottom: 0.25rem; +} + +.admin-content .page-container { + width: 95%; + max-width: none; + + @media (min-width: 768px) { + width: 90%; + } +} diff --git a/assets/controllers.json b/assets/controllers.json deleted file mode 100644 index 29ea244..0000000 --- a/assets/controllers.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "controllers": { - "@symfony/ux-turbo": { - "turbo-core": { - "enabled": true, - "fetch": "eager" - }, - "mercure-turbo-stream": { - "enabled": false, - "fetch": "eager" - } - } - }, - "entrypoints": [] -} diff --git a/assets/controllers/csrf_protection_controller.js b/assets/controllers/csrf_protection_controller.js deleted file mode 100644 index 511fffa..0000000 --- a/assets/controllers/csrf_protection_controller.js +++ /dev/null @@ -1,81 +0,0 @@ -const nameCheck = /^[-_a-zA-Z0-9]{4,22}$/; -const tokenCheck = /^[-_/+a-zA-Z0-9]{24,}$/; - -// Generate and double-submit a CSRF token in a form field and a cookie, as defined by Symfony's SameOriginCsrfTokenManager -// Use `form.requestSubmit()` to ensure that the submit event is triggered. Using `form.submit()` will not trigger the event -// and thus this event-listener will not be executed. -document.addEventListener('submit', function (event) { - generateCsrfToken(event.target); -}, true); - -// When @hotwired/turbo handles form submissions, send the CSRF token in a header in addition to a cookie -// The `framework.csrf_protection.check_header` config option needs to be enabled for the header to be checked -document.addEventListener('turbo:submit-start', function (event) { - const h = generateCsrfHeaders(event.detail.formSubmission.formElement); - Object.keys(h).map(function (k) { - event.detail.formSubmission.fetchRequest.headers[k] = h[k]; - }); -}); - -// When @hotwired/turbo handles form submissions, remove the CSRF cookie once a form has been submitted -document.addEventListener('turbo:submit-end', function (event) { - removeCsrfToken(event.detail.formSubmission.formElement); -}); - -export function generateCsrfToken (formElement) { - const csrfField = formElement.querySelector('input[data-controller="csrf-protection"], input[name="_csrf_token"]'); - - if (!csrfField) { - return; - } - - let csrfCookie = csrfField.getAttribute('data-csrf-protection-cookie-value'); - let csrfToken = csrfField.value; - - if (!csrfCookie && nameCheck.test(csrfToken)) { - csrfField.setAttribute('data-csrf-protection-cookie-value', csrfCookie = csrfToken); - csrfField.defaultValue = csrfToken = btoa(String.fromCharCode.apply(null, (window.crypto || window.msCrypto).getRandomValues(new Uint8Array(18)))); - } - csrfField.dispatchEvent(new Event('change', { bubbles: true })); - - if (csrfCookie && tokenCheck.test(csrfToken)) { - const cookie = csrfCookie + '_' + csrfToken + '=' + csrfCookie + '; path=/; samesite=strict'; - document.cookie = window.location.protocol === 'https:' ? '__Host-' + cookie + '; secure' : cookie; - } -} - -export function generateCsrfHeaders (formElement) { - const headers = {}; - const csrfField = formElement.querySelector('input[data-controller="csrf-protection"], input[name="_csrf_token"]'); - - if (!csrfField) { - return headers; - } - - const csrfCookie = csrfField.getAttribute('data-csrf-protection-cookie-value'); - - if (tokenCheck.test(csrfField.value) && nameCheck.test(csrfCookie)) { - headers[csrfCookie] = csrfField.value; - } - - return headers; -} - -export function removeCsrfToken (formElement) { - const csrfField = formElement.querySelector('input[data-controller="csrf-protection"], input[name="_csrf_token"]'); - - if (!csrfField) { - return; - } - - const csrfCookie = csrfField.getAttribute('data-csrf-protection-cookie-value'); - - if (tokenCheck.test(csrfField.value) && nameCheck.test(csrfCookie)) { - const cookie = csrfCookie + '_' + csrfField.value + '=0; path=/; samesite=strict; max-age=0'; - - document.cookie = window.location.protocol === 'https:' ? '__Host-' + cookie + '; secure' : cookie; - } -} - -/* stimulusFetch: 'lazy' */ -export default 'csrf-protection-controller'; diff --git a/assets/controllers/hello_controller.js b/assets/controllers/hello_controller.js deleted file mode 100644 index e847027..0000000 --- a/assets/controllers/hello_controller.js +++ /dev/null @@ -1,16 +0,0 @@ -import { Controller } from '@hotwired/stimulus'; - -/* - * This is an example Stimulus controller! - * - * Any element with a data-controller="hello" attribute will cause - * this controller to be executed. The name "hello" comes from the filename: - * hello_controller.js -> "hello" - * - * Delete this file or adapt it for your use! - */ -export default class extends Controller { - connect() { - this.element.textContent = 'Hello Stimulus! Edit me in assets/controllers/hello_controller.js'; - } -} diff --git a/assets/stimulus_bootstrap.js b/assets/stimulus_bootstrap.js deleted file mode 100644 index d4e50c9..0000000 --- a/assets/stimulus_bootstrap.js +++ /dev/null @@ -1,5 +0,0 @@ -import { startStimulusApp } from '@symfony/stimulus-bundle'; - -const app = startStimulusApp(); -// register any custom, 3rd party controllers here -// app.register('some_controller_name', SomeImportedController); diff --git a/assets/styles/app.css b/assets/styles/app.css deleted file mode 100644 index dd6181a..0000000 --- a/assets/styles/app.css +++ /dev/null @@ -1,3 +0,0 @@ -body { - background-color: skyblue; -} diff --git a/bun.lock b/bun.lock new file mode 100644 index 0000000..eaee3ff --- /dev/null +++ b/bun.lock @@ -0,0 +1,1468 @@ +{ + "lockfileVersion": 1, + "configVersion": 1, + "workspaces": { + "": { + "name": "e-cosplay", + "dependencies": { + "@grafikart/drop-files-element": "^1.0.9", + "@hotwired/turbo": "^8.0.23", + "@sentry/browser": "^10.38.0", + "@tailwindcss/vite": "^4.1.18", + "autoprefixer": "^10.4.24", + "tailwindcss": "^4.1.18", + "vite-plugin-compression": "^0.5.1", + "vite-plugin-favicon": "^1.0.8", + }, + "devDependencies": { + "@happy-dom/global-registrator": "^20.8.4", + "@hotwired/stimulus": "^3.0.0", + "@spomky-labs/pwa-bundle": "file:vendor/spomky-labs/pwa-bundle/assets", + "@tailwindcss/postcss": "^4.1.18", + "@vitest/coverage-v8": "^4.1.0", + "eslint": "9", + "idb": "^8.0.3", + "idb-keyval": "^6.2.2", + "postcss": "^8.5.6", + "postcss-scss": "^4.0.9", + "rollup-plugin-javascript-obfuscator": "^1.0.4", + "sass": "^1.97.3", + "stylelint": "16", + "stylelint-config-standard-scss": "13", + "vite": "^7.3.1", + "vitest": "^4.1.0", + }, + }, + }, + "packages": { + "@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="], + + "@babel/code-frame": ["@babel/code-frame@7.29.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw=="], + + "@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], + + "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="], + + "@babel/parser": ["@babel/parser@7.29.2", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA=="], + + "@babel/runtime": ["@babel/runtime@7.7.4", "", { "dependencies": { "regenerator-runtime": "^0.13.2" } }, "sha512-r24eVUUr0QqNZa+qrImUk8fn5SPhHq+IfYvIoIMg0do3GdK9sMdiLKP3GYVVaxpPKORgm8KRKaNTEhAjgIpLMw=="], + + "@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="], + + "@bcoe/v8-coverage": ["@bcoe/v8-coverage@1.0.2", "", {}, "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA=="], + + "@cacheable/memory": ["@cacheable/memory@2.0.8", "", { "dependencies": { "@cacheable/utils": "^2.4.0", "@keyv/bigmap": "^1.3.1", "hookified": "^1.15.1", "keyv": "^5.6.0" } }, "sha512-FvEb29x5wVwu/Kf93IWwsOOEuhHh6dYCJF3vcKLzXc0KXIW181AOzv6ceT4ZpBHDvAfG60eqb+ekmrnLHIy+jw=="], + + "@cacheable/utils": ["@cacheable/utils@2.4.1", "", { "dependencies": { "hashery": "^1.5.1", "keyv": "^5.6.0" } }, "sha512-eiFgzCbIneyMlLOmNG4g9xzF7Hv3Mga4LjxjcSC/ues6VYq2+gUbQI8JqNuw/ZM8tJIeIaBGpswAsqV2V7ApgA=="], + + "@csstools/css-parser-algorithms": ["@csstools/css-parser-algorithms@3.0.5", "", { "peerDependencies": { "@csstools/css-tokenizer": "^3.0.4" } }, "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ=="], + + "@csstools/css-syntax-patches-for-csstree": ["@csstools/css-syntax-patches-for-csstree@1.1.2", "", { "peerDependencies": { "css-tree": "^3.2.1" }, "optionalPeers": ["css-tree"] }, "sha512-5GkLzz4prTIpoyeUiIu3iV6CSG3Plo7xRVOFPKI7FVEJ3mZ0A8SwK0XU3Gl7xAkiQ+mDyam+NNp875/C5y+jSA=="], + + "@csstools/css-tokenizer": ["@csstools/css-tokenizer@3.0.4", "", {}, "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw=="], + + "@csstools/media-query-list-parser": ["@csstools/media-query-list-parser@4.0.3", "", { "peerDependencies": { "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4" } }, "sha512-HAYH7d3TLRHDOUQK4mZKf9k9Ph/m8Akstg66ywKR4SFAigjs3yBiUeZtFxywiTm5moZMAp/5W/ZuFnNXXYLuuQ=="], + + "@csstools/selector-specificity": ["@csstools/selector-specificity@5.0.0", "", { "peerDependencies": { "postcss-selector-parser": "^7.0.0" } }, "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw=="], + + "@dual-bundle/import-meta-resolve": ["@dual-bundle/import-meta-resolve@4.2.1", "", {}, "sha512-id+7YRUgoUX6CgV0DtuhirQWodeeA7Lf4i2x71JS/vtA5pRb/hIGWlw+G6MeXvsM+MXrz0VAydTGElX1rAfgPg=="], + + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.4", "", { "os": "aix", "cpu": "ppc64" }, "sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q=="], + + "@esbuild/android-arm": ["@esbuild/android-arm@0.27.4", "", { "os": "android", "cpu": "arm" }, "sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ=="], + + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.4", "", { "os": "android", "cpu": "arm64" }, "sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw=="], + + "@esbuild/android-x64": ["@esbuild/android-x64@0.27.4", "", { "os": "android", "cpu": "x64" }, "sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw=="], + + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ=="], + + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw=="], + + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.4", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw=="], + + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.4", "", { "os": "freebsd", "cpu": "x64" }, "sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ=="], + + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.4", "", { "os": "linux", "cpu": "arm" }, "sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg=="], + + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA=="], + + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.4", "", { "os": "linux", "cpu": "ia32" }, "sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA=="], + + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.4", "", { "os": "linux", "cpu": "none" }, "sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA=="], + + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.4", "", { "os": "linux", "cpu": "none" }, "sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw=="], + + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA=="], + + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.4", "", { "os": "linux", "cpu": "none" }, "sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw=="], + + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA=="], + + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.4", "", { "os": "linux", "cpu": "x64" }, "sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA=="], + + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.4", "", { "os": "none", "cpu": "arm64" }, "sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q=="], + + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.4", "", { "os": "none", "cpu": "x64" }, "sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg=="], + + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.4", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow=="], + + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.4", "", { "os": "openbsd", "cpu": "x64" }, "sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ=="], + + "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.4", "", { "os": "none", "cpu": "arm64" }, "sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg=="], + + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.4", "", { "os": "sunos", "cpu": "x64" }, "sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g=="], + + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg=="], + + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.4", "", { "os": "win32", "cpu": "ia32" }, "sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw=="], + + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.4", "", { "os": "win32", "cpu": "x64" }, "sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg=="], + + "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.1", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ=="], + + "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="], + + "@eslint/config-array": ["@eslint/config-array@0.21.2", "", { "dependencies": { "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", "minimatch": "^3.1.5" } }, "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw=="], + + "@eslint/config-helpers": ["@eslint/config-helpers@0.4.2", "", { "dependencies": { "@eslint/core": "^0.17.0" } }, "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw=="], + + "@eslint/core": ["@eslint/core@0.17.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ=="], + + "@eslint/eslintrc": ["@eslint/eslintrc@3.3.5", "", { "dependencies": { "ajv": "^6.14.0", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.1", "minimatch": "^3.1.5", "strip-json-comments": "^3.1.1" } }, "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg=="], + + "@eslint/js": ["@eslint/js@9.39.4", "", {}, "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw=="], + + "@eslint/object-schema": ["@eslint/object-schema@2.1.7", "", {}, "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA=="], + + "@eslint/plugin-kit": ["@eslint/plugin-kit@0.4.1", "", { "dependencies": { "@eslint/core": "^0.17.0", "levn": "^0.4.1" } }, "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA=="], + + "@grafikart/drop-files-element": ["@grafikart/drop-files-element@1.0.9", "", {}, "sha512-Eo2T8bQ30plnT+FOPW8xtGhe6+qDdbhT1IOeA0G18AiSI8nJF9LlmyZLh+PSy6UUnv3+6TMa3QOQIo/zroJQ4g=="], + + "@happy-dom/global-registrator": ["@happy-dom/global-registrator@20.8.9", "", { "dependencies": { "@types/node": ">=20.0.0", "happy-dom": "^20.8.9" } }, "sha512-DtZeRRHY9A/bisTJziUBBPrdnPui7+R185G/hzi6/Boymhqh7/wi53AY+IvQHS1+7OPaqfO/1XNpngNwthLz+A=="], + + "@hotwired/stimulus": ["@hotwired/stimulus@3.2.2", "", {}, "sha512-eGeIqNOQpXoPAIP7tC1+1Yc1yl1xnwYqg+3mzqxyrbE5pg5YFBZcA6YoTiByJB6DKAEsiWtl6tjTJS4IYtbB7A=="], + + "@hotwired/turbo": ["@hotwired/turbo@8.0.23", "", {}, "sha512-GZ7cijxEZ6Ig71u7rD6LHaRv/wcE/hNsc+nEfiWOkLNqUgLOwo5MNGWOy5ZV9ZUDSiQx1no7YxjTNnT4O6//cQ=="], + + "@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="], + + "@humanfs/node": ["@humanfs/node@0.16.7", "", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.4.0" } }, "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ=="], + + "@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="], + + "@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="], + + "@jimp/bmp": ["@jimp/bmp@0.16.13", "", { "dependencies": { "@babel/runtime": "^7.7.2", "@jimp/utils": "^0.16.13", "bmp-js": "^0.1.0" }, "peerDependencies": { "@jimp/custom": ">=0.3.5" } }, "sha512-9edAxu7N2FX7vzkdl5Jo1BbACfycUtBQX+XBMcHA2bk62P8R0otgkHg798frgAk/WxQIzwxqOH6wMiCwrlAzdQ=="], + + "@jimp/core": ["@jimp/core@0.16.13", "", { "dependencies": { "@babel/runtime": "^7.7.2", "@jimp/utils": "^0.16.13", "any-base": "^1.1.0", "buffer": "^5.2.0", "exif-parser": "^0.1.12", "file-type": "^16.5.4", "load-bmfont": "^1.3.1", "mkdirp": "^0.5.1", "phin": "^2.9.1", "pixelmatch": "^4.0.2", "tinycolor2": "^1.4.1" } }, "sha512-qXpA1tzTnlkTku9yqtuRtS/wVntvE6f3m3GNxdTdtmc+O+Wcg9Xo2ABPMh7Nc0AHbMKzwvwgB2JnjZmlmJEObg=="], + + "@jimp/custom": ["@jimp/custom@0.16.13", "", { "dependencies": { "@babel/runtime": "^7.7.2", "@jimp/core": "^0.16.13" } }, "sha512-LTATglVUPGkPf15zX1wTMlZ0+AU7cGEGF6ekVF1crA8eHUWsGjrYTB+Ht4E3HTrCok8weQG+K01rJndCp/l4XA=="], + + "@jimp/gif": ["@jimp/gif@0.16.13", "", { "dependencies": { "@babel/runtime": "^7.7.2", "@jimp/utils": "^0.16.13", "gifwrap": "^0.9.2", "omggif": "^1.0.9" }, "peerDependencies": { "@jimp/custom": ">=0.3.5" } }, "sha512-yFAMZGv3o+YcjXilMWWwS/bv1iSqykFahFMSO169uVMtfQVfa90kt4/kDwrXNR6Q9i6VHpFiGZMlF2UnHClBvg=="], + + "@jimp/jpeg": ["@jimp/jpeg@0.16.13", "", { "dependencies": { "@babel/runtime": "^7.7.2", "@jimp/utils": "^0.16.13", "jpeg-js": "^0.4.2" }, "peerDependencies": { "@jimp/custom": ">=0.3.5" } }, "sha512-BJHlDxzTlCqP2ThqP8J0eDrbBfod7npWCbJAcfkKqdQuFk0zBPaZ6KKaQKyKxmWJ87Z6ohANZoMKEbtvrwz1AA=="], + + "@jimp/plugin-blit": ["@jimp/plugin-blit@0.16.13", "", { "dependencies": { "@babel/runtime": "^7.7.2", "@jimp/utils": "^0.16.13" }, "peerDependencies": { "@jimp/custom": ">=0.3.5" } }, "sha512-8Z1k96ZFxlhK2bgrY1JNWNwvaBeI/bciLM0yDOni2+aZwfIIiC7Y6PeWHTAvjHNjphz+XCt01WQmOYWCn0ML6g=="], + + "@jimp/plugin-blur": ["@jimp/plugin-blur@0.16.13", "", { "dependencies": { "@babel/runtime": "^7.7.2", "@jimp/utils": "^0.16.13" }, "peerDependencies": { "@jimp/custom": ">=0.3.5" } }, "sha512-PvLrfa8vkej3qinlebyhLpksJgCF5aiysDMSVhOZqwH5nQLLtDE9WYbnsofGw4r0VVpyw3H/ANCIzYTyCtP9Cg=="], + + "@jimp/plugin-circle": ["@jimp/plugin-circle@0.16.13", "", { "dependencies": { "@babel/runtime": "^7.7.2", "@jimp/utils": "^0.16.13" }, "peerDependencies": { "@jimp/custom": ">=0.3.5" } }, "sha512-RNave7EFgZrb5V5EpdvJGAEHMnDAJuwv05hKscNfIYxf0kR3KhViBTDy+MoTnMlIvaKFULfwIgaZWzyhuINMzA=="], + + "@jimp/plugin-color": ["@jimp/plugin-color@0.16.13", "", { "dependencies": { "@babel/runtime": "^7.7.2", "@jimp/utils": "^0.16.13", "tinycolor2": "^1.4.1" }, "peerDependencies": { "@jimp/custom": ">=0.3.5" } }, "sha512-xW+9BtEvoIkkH/Wde9ql4nAFbYLkVINhpgAE7VcBUsuuB34WUbcBl/taOuUYQrPEFQJ4jfXiAJZ2H/rvKjCVnQ=="], + + "@jimp/plugin-contain": ["@jimp/plugin-contain@0.16.13", "", { "dependencies": { "@babel/runtime": "^7.7.2", "@jimp/utils": "^0.16.13" }, "peerDependencies": { "@jimp/custom": ">=0.3.5", "@jimp/plugin-blit": ">=0.3.5", "@jimp/plugin-resize": ">=0.3.5", "@jimp/plugin-scale": ">=0.3.5" } }, "sha512-QayTXw4tXMwU6q6acNTQrTTFTXpNRBe+MgTGMDU0lk+23PjlFCO/9sacflelG8lsp7vNHhAxFeHptDMAksEYzg=="], + + "@jimp/plugin-cover": ["@jimp/plugin-cover@0.16.13", "", { "dependencies": { "@babel/runtime": "^7.7.2", "@jimp/utils": "^0.16.13" }, "peerDependencies": { "@jimp/custom": ">=0.3.5", "@jimp/plugin-crop": ">=0.3.5", "@jimp/plugin-resize": ">=0.3.5", "@jimp/plugin-scale": ">=0.3.5" } }, "sha512-BSsP71GTNaqWRcvkbWuIVH+zK7b3TSNebbhDkFK0fVaUTzHuKMS/mgY4hDZIEVt7Rf5FjadAYtsujHN9w0iSYA=="], + + "@jimp/plugin-crop": ["@jimp/plugin-crop@0.16.13", "", { "dependencies": { "@babel/runtime": "^7.7.2", "@jimp/utils": "^0.16.13" }, "peerDependencies": { "@jimp/custom": ">=0.3.5" } }, "sha512-WEl2tPVYwzYL8OKme6Go2xqiWgKsgxlMwyHabdAU4tXaRwOCnOI7v4021gCcBb9zn/oWwguHuKHmK30Fw2Z/PA=="], + + "@jimp/plugin-displace": ["@jimp/plugin-displace@0.16.13", "", { "dependencies": { "@babel/runtime": "^7.7.2", "@jimp/utils": "^0.16.13" }, "peerDependencies": { "@jimp/custom": ">=0.3.5" } }, "sha512-qt9WKq8vWrcjySa9DyQ0x/RBMHQeiVjdVSY1SJsMjssPUf0pS74qorcuAkGi89biN3YoGUgPkpqECnAWnYwgGA=="], + + "@jimp/plugin-dither": ["@jimp/plugin-dither@0.16.13", "", { "dependencies": { "@babel/runtime": "^7.7.2", "@jimp/utils": "^0.16.13" }, "peerDependencies": { "@jimp/custom": ">=0.3.5" } }, "sha512-5/N3yJggbWQTlGZHQYJPmQXEwR52qaXjEzkp1yRBbtdaekXE3BG/suo0fqeoV/csf8ooI78sJzYmIrxNoWVtgQ=="], + + "@jimp/plugin-fisheye": ["@jimp/plugin-fisheye@0.16.13", "", { "dependencies": { "@babel/runtime": "^7.7.2", "@jimp/utils": "^0.16.13" }, "peerDependencies": { "@jimp/custom": ">=0.3.5" } }, "sha512-2rZmTdFbT/cF9lEZIkXCYO0TsT114Q27AX5IAo0Sju6jVQbvIk1dFUTnwLDadTo8wkJlFzGqMQ24Cs8cHWOliA=="], + + "@jimp/plugin-flip": ["@jimp/plugin-flip@0.16.13", "", { "dependencies": { "@babel/runtime": "^7.7.2", "@jimp/utils": "^0.16.13" }, "peerDependencies": { "@jimp/custom": ">=0.3.5", "@jimp/plugin-rotate": ">=0.3.5" } }, "sha512-EmcgAA74FTc5u7Z+hUO/sRjWwfPPLuOQP5O64x5g4j0T12Bd29IgsYZxoutZo/rb3579+JNa/3wsSEmyVv1EpA=="], + + "@jimp/plugin-gaussian": ["@jimp/plugin-gaussian@0.16.13", "", { "dependencies": { "@babel/runtime": "^7.7.2", "@jimp/utils": "^0.16.13" }, "peerDependencies": { "@jimp/custom": ">=0.3.5" } }, "sha512-A1XKfGQD0iDdIiKqFYi8nZMv4dDVYdxbrmgR7y/CzUHhSYdcmoljLIIsZZM3Iks/Wa353W3vtvkWLuDbQbch1w=="], + + "@jimp/plugin-invert": ["@jimp/plugin-invert@0.16.13", "", { "dependencies": { "@babel/runtime": "^7.7.2", "@jimp/utils": "^0.16.13" }, "peerDependencies": { "@jimp/custom": ">=0.3.5" } }, "sha512-xFMrIn7czEZbdbMzZWuaZFnlLGJDVJ82y5vlsKsXRTG2kcxRsMPXvZRWHV57nSs1YFsNqXSbrC8B98n0E32njQ=="], + + "@jimp/plugin-mask": ["@jimp/plugin-mask@0.16.13", "", { "dependencies": { "@babel/runtime": "^7.7.2", "@jimp/utils": "^0.16.13" }, "peerDependencies": { "@jimp/custom": ">=0.3.5" } }, "sha512-wLRYKVBXql2GAYgt6FkTnCfE+q5NomM7Dlh0oIPGAoMBWDyTx0eYutRK6PlUrRK2yMHuroAJCglICTbxqGzowQ=="], + + "@jimp/plugin-normalize": ["@jimp/plugin-normalize@0.16.13", "", { "dependencies": { "@babel/runtime": "^7.7.2", "@jimp/utils": "^0.16.13" }, "peerDependencies": { "@jimp/custom": ">=0.3.5" } }, "sha512-3tfad0n9soRna4IfW9NzQdQ2Z3ijkmo21DREHbE6CGcMIxOSvfRdSvf1qQPApxjTSo8LTU4MCi/fidx/NZ0GqQ=="], + + "@jimp/plugin-print": ["@jimp/plugin-print@0.16.13", "", { "dependencies": { "@babel/runtime": "^7.7.2", "@jimp/utils": "^0.16.13", "load-bmfont": "^1.4.0" }, "peerDependencies": { "@jimp/custom": ">=0.3.5", "@jimp/plugin-blit": ">=0.3.5" } }, "sha512-0m6i3p01PGRkGAK9r53hDYrkyMq+tlhLOIbsSTmZyh6HLshUKlTB7eXskF5OpVd5ZUHoltlNc6R+ggvKIzxRFw=="], + + "@jimp/plugin-resize": ["@jimp/plugin-resize@0.16.13", "", { "dependencies": { "@babel/runtime": "^7.7.2", "@jimp/utils": "^0.16.13" }, "peerDependencies": { "@jimp/custom": ">=0.3.5" } }, "sha512-qoqtN8LDknm3fJm9nuPygJv30O3vGhSBD2TxrsCnhtOsxKAqVPJtFVdGd/qVuZ8nqQANQmTlfqTiK9mVWQ7MiQ=="], + + "@jimp/plugin-rotate": ["@jimp/plugin-rotate@0.16.13", "", { "dependencies": { "@babel/runtime": "^7.7.2", "@jimp/utils": "^0.16.13" }, "peerDependencies": { "@jimp/custom": ">=0.3.5", "@jimp/plugin-blit": ">=0.3.5", "@jimp/plugin-crop": ">=0.3.5", "@jimp/plugin-resize": ">=0.3.5" } }, "sha512-Ev+Jjmj1nHYw897z9C3R9dYsPv7S2/nxdgfFb/h8hOwK0Ovd1k/+yYS46A0uj/JCKK0pQk8wOslYBkPwdnLorw=="], + + "@jimp/plugin-scale": ["@jimp/plugin-scale@0.16.13", "", { "dependencies": { "@babel/runtime": "^7.7.2", "@jimp/utils": "^0.16.13" }, "peerDependencies": { "@jimp/custom": ">=0.3.5", "@jimp/plugin-resize": ">=0.3.5" } }, "sha512-05POQaEJVucjTiSGMoH68ZiELc7QqpIpuQlZ2JBbhCV+WCbPFUBcGSmE7w4Jd0E2GvCho/NoMODLwgcVGQA97A=="], + + "@jimp/plugin-shadow": ["@jimp/plugin-shadow@0.16.13", "", { "dependencies": { "@babel/runtime": "^7.7.2", "@jimp/utils": "^0.16.13" }, "peerDependencies": { "@jimp/custom": ">=0.3.5", "@jimp/plugin-blur": ">=0.3.5", "@jimp/plugin-resize": ">=0.3.5" } }, "sha512-nmu5VSZ9hsB1JchTKhnnCY+paRBnwzSyK5fhkhtQHHoFD5ArBQ/5wU8y6tCr7k/GQhhGq1OrixsECeMjPoc8Zw=="], + + "@jimp/plugin-threshold": ["@jimp/plugin-threshold@0.16.13", "", { "dependencies": { "@babel/runtime": "^7.7.2", "@jimp/utils": "^0.16.13" }, "peerDependencies": { "@jimp/custom": ">=0.3.5", "@jimp/plugin-color": ">=0.8.0", "@jimp/plugin-resize": ">=0.8.0" } }, "sha512-+3zArBH0OE3Rhjm4HyAokMsZlIq5gpQec33CncyoSwxtRBM2WAhUVmCUKuBo+Lr/2/4ISoY4BWpHKhMLDix6cA=="], + + "@jimp/plugins": ["@jimp/plugins@0.16.13", "", { "dependencies": { "@babel/runtime": "^7.7.2", "@jimp/plugin-blit": "^0.16.13", "@jimp/plugin-blur": "^0.16.13", "@jimp/plugin-circle": "^0.16.13", "@jimp/plugin-color": "^0.16.13", "@jimp/plugin-contain": "^0.16.13", "@jimp/plugin-cover": "^0.16.13", "@jimp/plugin-crop": "^0.16.13", "@jimp/plugin-displace": "^0.16.13", "@jimp/plugin-dither": "^0.16.13", "@jimp/plugin-fisheye": "^0.16.13", "@jimp/plugin-flip": "^0.16.13", "@jimp/plugin-gaussian": "^0.16.13", "@jimp/plugin-invert": "^0.16.13", "@jimp/plugin-mask": "^0.16.13", "@jimp/plugin-normalize": "^0.16.13", "@jimp/plugin-print": "^0.16.13", "@jimp/plugin-resize": "^0.16.13", "@jimp/plugin-rotate": "^0.16.13", "@jimp/plugin-scale": "^0.16.13", "@jimp/plugin-shadow": "^0.16.13", "@jimp/plugin-threshold": "^0.16.13", "timm": "^1.6.1" }, "peerDependencies": { "@jimp/custom": ">=0.3.5" } }, "sha512-CJLdqODEhEVs4MgWCxpWL5l95sCBlkuSLz65cxEm56X5akIsn4LOlwnKoSEZioYcZUBvHhCheH67AyPTudfnQQ=="], + + "@jimp/png": ["@jimp/png@0.16.13", "", { "dependencies": { "@babel/runtime": "^7.7.2", "@jimp/utils": "^0.16.13", "pngjs": "^3.3.3" }, "peerDependencies": { "@jimp/custom": ">=0.3.5" } }, "sha512-8cGqINvbWJf1G0Her9zbq9I80roEX0A+U45xFby3tDWfzn+Zz8XKDF1Nv9VUwVx0N3zpcG1RPs9hfheG4Cq2kg=="], + + "@jimp/tiff": ["@jimp/tiff@0.16.13", "", { "dependencies": { "@babel/runtime": "^7.7.2", "utif": "^2.0.1" }, "peerDependencies": { "@jimp/custom": ">=0.3.5" } }, "sha512-oJY8d9u95SwW00VPHuCNxPap6Q1+E/xM5QThb9Hu+P6EGuu6lIeLaNBMmFZyblwFbwrH+WBOZlvIzDhi4Dm/6Q=="], + + "@jimp/types": ["@jimp/types@0.16.13", "", { "dependencies": { "@babel/runtime": "^7.7.2", "@jimp/bmp": "^0.16.13", "@jimp/gif": "^0.16.13", "@jimp/jpeg": "^0.16.13", "@jimp/png": "^0.16.13", "@jimp/tiff": "^0.16.13", "timm": "^1.6.1" }, "peerDependencies": { "@jimp/custom": ">=0.3.5" } }, "sha512-mC0yVNUobFDjoYLg4hoUwzMKgNlxynzwt3cDXzumGvRJ7Kb8qQGOWJQjQFo5OxmGExqzPphkirdbBF88RVLBCg=="], + + "@jimp/utils": ["@jimp/utils@0.16.13", "", { "dependencies": { "@babel/runtime": "^7.7.2", "regenerator-runtime": "^0.13.3" } }, "sha512-VyCpkZzFTHXtKgVO35iKN0sYR10psGpV6SkcSeV4oF7eSYlR8Bl6aQLCzVeFjvESF7mxTmIiI3/XrMobVrtxDA=="], + + "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], + + "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="], + + "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], + + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], + + "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], + + "@keyv/bigmap": ["@keyv/bigmap@1.3.1", "", { "dependencies": { "hashery": "^1.4.0", "hookified": "^1.15.0" }, "peerDependencies": { "keyv": "^5.6.0" } }, "sha512-WbzE9sdmQtKy8vrNPa9BRnwZh5UF4s1KTmSK0KUVLo3eff5BlQNNWDnFOouNpKfPKDnms9xynJjsMYjMaT/aFQ=="], + + "@keyv/serialize": ["@keyv/serialize@1.1.1", "", {}, "sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA=="], + + "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], + + "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="], + + "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="], + + "@parcel/watcher": ["@parcel/watcher@2.5.6", "", { "dependencies": { "detect-libc": "^2.0.3", "is-glob": "^4.0.3", "node-addon-api": "^7.0.0", "picomatch": "^4.0.3" }, "optionalDependencies": { "@parcel/watcher-android-arm64": "2.5.6", "@parcel/watcher-darwin-arm64": "2.5.6", "@parcel/watcher-darwin-x64": "2.5.6", "@parcel/watcher-freebsd-x64": "2.5.6", "@parcel/watcher-linux-arm-glibc": "2.5.6", "@parcel/watcher-linux-arm-musl": "2.5.6", "@parcel/watcher-linux-arm64-glibc": "2.5.6", "@parcel/watcher-linux-arm64-musl": "2.5.6", "@parcel/watcher-linux-x64-glibc": "2.5.6", "@parcel/watcher-linux-x64-musl": "2.5.6", "@parcel/watcher-win32-arm64": "2.5.6", "@parcel/watcher-win32-ia32": "2.5.6", "@parcel/watcher-win32-x64": "2.5.6" } }, "sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ=="], + + "@parcel/watcher-android-arm64": ["@parcel/watcher-android-arm64@2.5.6", "", { "os": "android", "cpu": "arm64" }, "sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A=="], + + "@parcel/watcher-darwin-arm64": ["@parcel/watcher-darwin-arm64@2.5.6", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA=="], + + "@parcel/watcher-darwin-x64": ["@parcel/watcher-darwin-x64@2.5.6", "", { "os": "darwin", "cpu": "x64" }, "sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg=="], + + "@parcel/watcher-freebsd-x64": ["@parcel/watcher-freebsd-x64@2.5.6", "", { "os": "freebsd", "cpu": "x64" }, "sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng=="], + + "@parcel/watcher-linux-arm-glibc": ["@parcel/watcher-linux-arm-glibc@2.5.6", "", { "os": "linux", "cpu": "arm" }, "sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ=="], + + "@parcel/watcher-linux-arm-musl": ["@parcel/watcher-linux-arm-musl@2.5.6", "", { "os": "linux", "cpu": "arm" }, "sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg=="], + + "@parcel/watcher-linux-arm64-glibc": ["@parcel/watcher-linux-arm64-glibc@2.5.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA=="], + + "@parcel/watcher-linux-arm64-musl": ["@parcel/watcher-linux-arm64-musl@2.5.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA=="], + + "@parcel/watcher-linux-x64-glibc": ["@parcel/watcher-linux-x64-glibc@2.5.6", "", { "os": "linux", "cpu": "x64" }, "sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ=="], + + "@parcel/watcher-linux-x64-musl": ["@parcel/watcher-linux-x64-musl@2.5.6", "", { "os": "linux", "cpu": "x64" }, "sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg=="], + + "@parcel/watcher-win32-arm64": ["@parcel/watcher-win32-arm64@2.5.6", "", { "os": "win32", "cpu": "arm64" }, "sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q=="], + + "@parcel/watcher-win32-ia32": ["@parcel/watcher-win32-ia32@2.5.6", "", { "os": "win32", "cpu": "ia32" }, "sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g=="], + + "@parcel/watcher-win32-x64": ["@parcel/watcher-win32-x64@2.5.6", "", { "os": "win32", "cpu": "x64" }, "sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw=="], + + "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.60.1", "", { "os": "android", "cpu": "arm" }, "sha512-d6FinEBLdIiK+1uACUttJKfgZREXrF0Qc2SmLII7W2AD8FfiZ9Wjd+rD/iRuf5s5dWrr1GgwXCvPqOuDquOowA=="], + + "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.60.1", "", { "os": "android", "cpu": "arm64" }, "sha512-YjG/EwIDvvYI1YvYbHvDz/BYHtkY4ygUIXHnTdLhG+hKIQFBiosfWiACWortsKPKU/+dUwQQCKQM3qrDe8c9BA=="], + + "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.60.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-mjCpF7GmkRtSJwon+Rq1N8+pI+8l7w5g9Z3vWj4T7abguC4Czwi3Yu/pFaLvA3TTeMVjnu3ctigusqWUfjZzvw=="], + + "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.60.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-haZ7hJ1JT4e9hqkoT9R/19XW2QKqjfJVv+i5AGg57S+nLk9lQnJ1F/eZloRO3o9Scy9CM3wQ9l+dkXtcBgN5Ew=="], + + "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.60.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-czw90wpQq3ZsAVBlinZjAYTKduOjTywlG7fEeWKUA7oCmpA8xdTkxZZlwNJKWqILlq0wehoZcJYfBvOyhPTQ6w=="], + + "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.60.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-KVB2rqsxTHuBtfOeySEyzEOB7ltlB/ux38iu2rBQzkjbwRVlkhAGIEDiiYnO2kFOkJp+Z7pUXKyrRRFuFUKt+g=="], + + "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.60.1", "", { "os": "linux", "cpu": "arm" }, "sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g=="], + + "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.60.1", "", { "os": "linux", "cpu": "arm" }, "sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg=="], + + "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.60.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ=="], + + "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.60.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA=="], + + "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.60.1", "", { "os": "linux", "cpu": "none" }, "sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ=="], + + "@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.60.1", "", { "os": "linux", "cpu": "none" }, "sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw=="], + + "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.60.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw=="], + + "@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.60.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg=="], + + "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.60.1", "", { "os": "linux", "cpu": "none" }, "sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg=="], + + "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.60.1", "", { "os": "linux", "cpu": "none" }, "sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg=="], + + "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.60.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ=="], + + "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.60.1", "", { "os": "linux", "cpu": "x64" }, "sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg=="], + + "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.60.1", "", { "os": "linux", "cpu": "x64" }, "sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w=="], + + "@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.60.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw=="], + + "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.60.1", "", { "os": "none", "cpu": "arm64" }, "sha512-4Cv23ZrONRbNtbZa37mLSueXUCtN7MXccChtKpUnQNgF010rjrjfHx3QxkS2PI7LqGT5xXyYs1a7LbzAwT0iCA=="], + + "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.60.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-i1okWYkA4FJICtr7KpYzFpRTHgy5jdDbZiWfvny21iIKky5YExiDXP+zbXzm3dUcFpkEeYNHgQ5fuG236JPq0g=="], + + "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.60.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-u09m3CuwLzShA0EYKMNiFgcjjzwqtUMLmuCJLeZWjjOYA3IT2Di09KaxGBTP9xVztWyIWjVdsB2E9goMjZvTQg=="], + + "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.60.1", "", { "os": "win32", "cpu": "x64" }, "sha512-k+600V9Zl1CM7eZxJgMyTUzmrmhB/0XZnF4pRypKAlAgxmedUA+1v9R+XOFv56W4SlHEzfeMtzujLJD22Uz5zg=="], + + "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.60.1", "", { "os": "win32", "cpu": "x64" }, "sha512-lWMnixq/QzxyhTV6NjQJ4SFo1J6PvOX8vUx5Wb4bBPsEb+8xZ89Bz6kOXpfXj9ak9AHTQVQzlgzBEc1SyM27xQ=="], + + "@sentry-internal/browser-utils": ["@sentry-internal/browser-utils@10.46.0", "", { "dependencies": { "@sentry/core": "10.46.0" } }, "sha512-WB1gBT9G13V02ekZ6NpUhoI1aGHV2eNfjEPthkU2bGBvFpQKnstwzjg7waIRGR7cu+YSW2Q6UI6aQLgBeOPD1g=="], + + "@sentry-internal/feedback": ["@sentry-internal/feedback@10.46.0", "", { "dependencies": { "@sentry/core": "10.46.0" } }, "sha512-c4pI/z9nZCQXe9GYEw/hE/YTY9AxGBp8/wgKI+T8zylrN35SGHaXv63szzE1WbI8lacBY8lBF7rstq9bQVCaHw=="], + + "@sentry-internal/replay": ["@sentry-internal/replay@10.46.0", "", { "dependencies": { "@sentry-internal/browser-utils": "10.46.0", "@sentry/core": "10.46.0" } }, "sha512-JBsWeXG6bRbxBFK8GzWymWGOB9QE7Kl57BeF3jzgdHTuHSWZ2mRnAmb1K05T4LU+gVygk6yW0KmdC8Py9Qzg9A=="], + + "@sentry-internal/replay-canvas": ["@sentry-internal/replay-canvas@10.46.0", "", { "dependencies": { "@sentry-internal/replay": "10.46.0", "@sentry/core": "10.46.0" } }, "sha512-ub314MWUsekVCuoH0/HJbbimlI24SkV745UW2pj9xRbxOAEf1wjkmIzxKrMDbTgJGuEunug02XZVdJFJUzOcDw=="], + + "@sentry/browser": ["@sentry/browser@10.46.0", "", { "dependencies": { "@sentry-internal/browser-utils": "10.46.0", "@sentry-internal/feedback": "10.46.0", "@sentry-internal/replay": "10.46.0", "@sentry-internal/replay-canvas": "10.46.0", "@sentry/core": "10.46.0" } }, "sha512-80DmGlTk5Z2/OxVOzLNxwolMyouuAYKqG8KUcoyintZqHbF6kO1RulI610HmyUt3OagKeBCqt9S7w0VIfCRL+Q=="], + + "@sentry/core": ["@sentry/core@10.46.0", "", {}, "sha512-N3fj4zqBQOhXliS1Ne9euqIKuciHCGOJfPGQLwBoW9DNz03jF+NB8+dUKtrJ79YLoftjVgf8nbgwtADK7NR+2Q=="], + + "@spomky-labs/pwa-bundle": ["@spomky-labs/pwa-bundle@file:vendor/spomky-labs/pwa-bundle/assets", { "devDependencies": { "@hotwired/stimulus": "^3.0.0", "idb": "^8.0", "idb-keyval": "^6.2" }, "peerDependencies": { "@hotwired/stimulus": "^3.0.0", "idb": "^8.0", "idb-keyval": "^6.2" } }], + + "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + + "@tailwindcss/node": ["@tailwindcss/node@4.2.2", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.19.0", "jiti": "^2.6.1", "lightningcss": "1.32.0", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.2.2" } }, "sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA=="], + + "@tailwindcss/oxide": ["@tailwindcss/oxide@4.2.2", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.2.2", "@tailwindcss/oxide-darwin-arm64": "4.2.2", "@tailwindcss/oxide-darwin-x64": "4.2.2", "@tailwindcss/oxide-freebsd-x64": "4.2.2", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.2", "@tailwindcss/oxide-linux-arm64-gnu": "4.2.2", "@tailwindcss/oxide-linux-arm64-musl": "4.2.2", "@tailwindcss/oxide-linux-x64-gnu": "4.2.2", "@tailwindcss/oxide-linux-x64-musl": "4.2.2", "@tailwindcss/oxide-wasm32-wasi": "4.2.2", "@tailwindcss/oxide-win32-arm64-msvc": "4.2.2", "@tailwindcss/oxide-win32-x64-msvc": "4.2.2" } }, "sha512-qEUA07+E5kehxYp9BVMpq9E8vnJuBHfJEC0vPC5e7iL/hw7HR61aDKoVoKzrG+QKp56vhNZe4qwkRmMC0zDLvg=="], + + "@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.2.2", "", { "os": "android", "cpu": "arm64" }, "sha512-dXGR1n+P3B6748jZO/SvHZq7qBOqqzQ+yFrXpoOWWALWndF9MoSKAT3Q0fYgAzYzGhxNYOoysRvYlpixRBBoDg=="], + + "@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.2.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-iq9Qjr6knfMpZHj55/37ouZeykwbDqF21gPFtfnhCCKGDcPI/21FKC9XdMO/XyBM7qKORx6UIhGgg6jLl7BZlg=="], + + "@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.2.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-BlR+2c3nzc8f2G639LpL89YY4bdcIdUmiOOkv2GQv4/4M0vJlpXEa0JXNHhCHU7VWOKWT/CjqHdTP8aUuDJkuw=="], + + "@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.2.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-YUqUgrGMSu2CDO82hzlQ5qSb5xmx3RUrke/QgnoEx7KvmRJHQuZHZmZTLSuuHwFf0DJPybFMXMYf+WJdxHy/nQ=="], + + "@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.2.2", "", { "os": "linux", "cpu": "arm" }, "sha512-FPdhvsW6g06T9BWT0qTwiVZYE2WIFo2dY5aCSpjG/S/u1tby+wXoslXS0kl3/KXnULlLr1E3NPRRw0g7t2kgaQ=="], + + "@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.2.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-4og1V+ftEPXGttOO7eCmW7VICmzzJWgMx+QXAJRAhjrSjumCwWqMfkDrNu1LXEQzNAwz28NCUpucgQPrR4S2yw=="], + + "@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.2.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-oCfG/mS+/+XRlwNjnsNLVwnMWYH7tn/kYPsNPh+JSOMlnt93mYNCKHYzylRhI51X+TbR+ufNhhKKzm6QkqX8ag=="], + + "@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.2.2", "", { "os": "linux", "cpu": "x64" }, "sha512-rTAGAkDgqbXHNp/xW0iugLVmX62wOp2PoE39BTCGKjv3Iocf6AFbRP/wZT/kuCxC9QBh9Pu8XPkv/zCZB2mcMg=="], + + "@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.2.2", "", { "os": "linux", "cpu": "x64" }, "sha512-XW3t3qwbIwiSyRCggeO2zxe3KWaEbM0/kW9e8+0XpBgyKU4ATYzcVSMKteZJ1iukJ3HgHBjbg9P5YPRCVUxlnQ=="], + + "@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.2.2", "", { "dependencies": { "@emnapi/core": "^1.8.1", "@emnapi/runtime": "^1.8.1", "@emnapi/wasi-threads": "^1.1.0", "@napi-rs/wasm-runtime": "^1.1.1", "@tybys/wasm-util": "^0.10.1", "tslib": "^2.8.1" }, "cpu": "none" }, "sha512-eKSztKsmEsn1O5lJ4ZAfyn41NfG7vzCg496YiGtMDV86jz1q/irhms5O0VrY6ZwTUkFy/EKG3RfWgxSI3VbZ8Q=="], + + "@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.2.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-qPmaQM4iKu5mxpsrWZMOZRgZv1tOZpUm+zdhhQP0VhJfyGGO3aUKdbh3gDZc/dPLQwW4eSqWGrrcWNBZWUWaXQ=="], + + "@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.2.2", "", { "os": "win32", "cpu": "x64" }, "sha512-1T/37VvI7WyH66b+vqHj/cLwnCxt7Qt3WFu5Q8hk65aOvlwAhs7rAp1VkulBJw/N4tMirXjVnylTR72uI0HGcA=="], + + "@tailwindcss/postcss": ["@tailwindcss/postcss@4.2.2", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "@tailwindcss/node": "4.2.2", "@tailwindcss/oxide": "4.2.2", "postcss": "^8.5.6", "tailwindcss": "4.2.2" } }, "sha512-n4goKQbW8RVXIbNKRB/45LzyUqN451deQK0nzIeauVEqjlI49slUlgKYJM2QyUzap/PcpnS7kzSUmPb1sCRvYQ=="], + + "@tailwindcss/vite": ["@tailwindcss/vite@4.2.2", "", { "dependencies": { "@tailwindcss/node": "4.2.2", "@tailwindcss/oxide": "4.2.2", "tailwindcss": "4.2.2" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7 || ^8" } }, "sha512-mEiF5HO1QqCLXoNEfXVA1Tzo+cYsrqV7w9Juj2wdUFyW07JRenqMG225MvPwr3ZD9N1bFQj46X7r33iHxLUW0w=="], + + "@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="], + + "@types/chai": ["@types/chai@5.2.3", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="], + + "@types/deep-eql": ["@types/deep-eql@4.0.2", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="], + + "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], + + "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], + + "@types/minimatch": ["@types/minimatch@3.0.5", "", {}, "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ=="], + + "@types/node": ["@types/node@25.5.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw=="], + + "@types/validator": ["@types/validator@10.11.3", "", {}, "sha512-GKF2VnEkMmEeEGvoo03ocrP9ySMuX1ypKazIYMlsjfslfBMhOAtC5dmEWKdJioW4lJN7MZRS88kalTsVClyQ9w=="], + + "@types/whatwg-mimetype": ["@types/whatwg-mimetype@3.0.2", "", {}, "sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA=="], + + "@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="], + + "@vitest/coverage-v8": ["@vitest/coverage-v8@4.1.2", "", { "dependencies": { "@bcoe/v8-coverage": "^1.0.2", "@vitest/utils": "4.1.2", "ast-v8-to-istanbul": "^1.0.0", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", "istanbul-reports": "^3.2.0", "magicast": "^0.5.2", "obug": "^2.1.1", "std-env": "^4.0.0-rc.1", "tinyrainbow": "^3.1.0" }, "peerDependencies": { "@vitest/browser": "4.1.2", "vitest": "4.1.2" }, "optionalPeers": ["@vitest/browser"] }, "sha512-sPK//PHO+kAkScb8XITeB1bf7fsk85Km7+rt4eeuRR3VS1/crD47cmV5wicisJmjNdfeokTZwjMk4Mj2d58Mgg=="], + + "@vitest/expect": ["@vitest/expect@4.1.2", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.1.2", "@vitest/utils": "4.1.2", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" } }, "sha512-gbu+7B0YgUJ2nkdsRJrFFW6X7NTP44WlhiclHniUhxADQJH5Szt9mZ9hWnJPJ8YwOK5zUOSSlSvyzRf0u1DSBQ=="], + + "@vitest/mocker": ["@vitest/mocker@4.1.2", "", { "dependencies": { "@vitest/spy": "4.1.2", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["msw", "vite"] }, "sha512-Ize4iQtEALHDttPRCmN+FKqOl2vxTiNUhzobQFFt/BM1lRUTG7zRCLOykG/6Vo4E4hnUdfVLo5/eqKPukcWW7Q=="], + + "@vitest/pretty-format": ["@vitest/pretty-format@4.1.2", "", { "dependencies": { "tinyrainbow": "^3.1.0" } }, "sha512-dwQga8aejqeuB+TvXCMzSQemvV9hNEtDDpgUKDzOmNQayl2OG241PSWeJwKRH3CiC+sESrmoFd49rfnq7T4RnA=="], + + "@vitest/runner": ["@vitest/runner@4.1.2", "", { "dependencies": { "@vitest/utils": "4.1.2", "pathe": "^2.0.3" } }, "sha512-Gr+FQan34CdiYAwpGJmQG8PgkyFVmARK8/xSijia3eTFgVfpcpztWLuP6FttGNfPLJhaZVP/euvujeNYar36OQ=="], + + "@vitest/snapshot": ["@vitest/snapshot@4.1.2", "", { "dependencies": { "@vitest/pretty-format": "4.1.2", "@vitest/utils": "4.1.2", "magic-string": "^0.30.21", "pathe": "^2.0.3" } }, "sha512-g7yfUmxYS4mNxk31qbOYsSt2F4m1E02LFqO53Xpzg3zKMhLAPZAjjfyl9e6z7HrW6LvUdTwAQR3HHfLjpko16A=="], + + "@vitest/spy": ["@vitest/spy@4.1.2", "", {}, "sha512-DU4fBnbVCJGNBwVA6xSToNXrkZNSiw59H8tcuUspVMsBDBST4nfvsPsEHDHGtWRRnqBERBQu7TrTKskmjqTXKA=="], + + "@vitest/utils": ["@vitest/utils@4.1.2", "", { "dependencies": { "@vitest/pretty-format": "4.1.2", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" } }, "sha512-xw2/TiX82lQHA06cgbqRKFb5lCAy3axQ4H4SoUFhUsg+wztiet+co86IAMDtF6Vm1hc7J6j09oh/rgDn+JdKIQ=="], + + "abort-controller": ["abort-controller@3.0.0", "", { "dependencies": { "event-target-shim": "^5.0.0" } }, "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg=="], + + "acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="], + + "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], + + "ajv": ["ajv@6.14.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw=="], + + "amdefine": ["amdefine@1.0.1", "", {}, "sha512-S2Hw0TtNkMJhIabBwIojKL9YHO5T0n5eNqWJ7Lrlel/zDbftQpxpapi8tZs3X1HWa+u+QeydGmzzNU0m09+Rcg=="], + + "ansi-escapes": ["ansi-escapes@1.4.0", "", {}, "sha512-wiXutNjDUlNEDWHcYH3jtZUhd3c4/VojassD8zHdHCY13xbZy2XbW+NKQwA0tWGBVzDA9qEzYwfoSsWmviidhw=="], + + "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + + "any-base": ["any-base@1.1.0", "", {}, "sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg=="], + + "aproba": ["aproba@1.2.0", "", {}, "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw=="], + + "are-we-there-yet": ["are-we-there-yet@1.1.7", "", { "dependencies": { "delegates": "^1.0.0", "readable-stream": "^2.0.6" } }, "sha512-nxwy40TuMiUGqMyRHgCSWZ9FM4VAoRP4xUYSTv5ImRog+h9yISPbVH7H8fASCIzYn9wlEv4zvFL7uKDMCFQm3g=="], + + "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + + "array-differ": ["array-differ@3.0.0", "", {}, "sha512-THtfYS6KtME/yIAhKjZ2ul7XI96lQGHRputJQHO80LAWQnuGP4iCIN8vdMRboGbIEYBwU33q8Tch1os2+X0kMg=="], + + "array-union": ["array-union@2.1.0", "", {}, "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw=="], + + "arrify": ["arrify@2.0.1", "", {}, "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug=="], + + "asn1": ["asn1@0.2.6", "", { "dependencies": { "safer-buffer": "~2.1.0" } }, "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ=="], + + "assert-plus": ["assert-plus@1.0.0", "", {}, "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw=="], + + "assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="], + + "ast-v8-to-istanbul": ["ast-v8-to-istanbul@1.0.0", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.31", "estree-walker": "^3.0.3", "js-tokens": "^10.0.0" } }, "sha512-1fSfIwuDICFA4LKkCzRPO7F0hzFf0B7+Xqrl27ynQaa+Rh0e1Es0v6kWHPott3lU10AyAr7oKHa65OppjLn3Rg=="], + + "astral-regex": ["astral-regex@2.0.0", "", {}, "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ=="], + + "asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="], + + "author-regex": ["author-regex@1.0.0", "", {}, "sha512-KbWgR8wOYRAPekEmMXrYYdc7BRyhn2Ftk7KWfMUnQ43hFdojWEFRxhhRUm3/OFEdPa1r0KAvTTg9YQK57xTe0g=="], + + "autoprefixer": ["autoprefixer@10.4.27", "", { "dependencies": { "browserslist": "^4.28.1", "caniuse-lite": "^1.0.30001774", "fraction.js": "^5.3.4", "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.1.0" }, "bin": { "autoprefixer": "bin/autoprefixer" } }, "sha512-NP9APE+tO+LuJGn7/9+cohklunJsXWiaWEfV3si4Gi/XHDwVNgkwr1J3RQYFIvPy76GmJ9/bW8vyoU1LcxwKHA=="], + + "aws-sign2": ["aws-sign2@0.7.0", "", {}, "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA=="], + + "aws4": ["aws4@1.13.2", "", {}, "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw=="], + + "babel-polyfill": ["babel-polyfill@6.23.0", "", { "dependencies": { "babel-runtime": "^6.22.0", "core-js": "^2.4.0", "regenerator-runtime": "^0.10.0" } }, "sha512-0l7mVU+LrQ2X/ZTUq63T5i3VyR2aTgcRTFmBcD6djQ/Fek6q1A9t5u0F4jZVYHzp78jwWAzGfLpAY1b4/I3lfg=="], + + "babel-runtime": ["babel-runtime@6.26.0", "", { "dependencies": { "core-js": "^2.4.0", "regenerator-runtime": "^0.11.0" } }, "sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g=="], + + "balanced-match": ["balanced-match@2.0.0", "", {}, "sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA=="], + + "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], + + "baseline-browser-mapping": ["baseline-browser-mapping@2.10.12", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-qyq26DxfY4awP2gIRXhhLWfwzwI+N5Nxk6iQi8EFizIaWIjqicQTE4sLnZZVdeKPRcVNoJOkkpfzoIYuvCKaIQ=="], + + "bcrypt-pbkdf": ["bcrypt-pbkdf@1.0.2", "", { "dependencies": { "tweetnacl": "^0.14.3" } }, "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w=="], + + "bignumber.js": ["bignumber.js@2.4.0", "", {}, "sha512-uw4ra6Cv483Op/ebM0GBKKfxZlSmn6NgFRby5L3yGTlunLj53KQgndDlqy2WVFOwgvurocApYkSud0aO+mvrpQ=="], + + "bl": ["bl@4.1.0", "", { "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w=="], + + "bmp-js": ["bmp-js@0.0.1", "", {}, "sha512-OS74Rlt0Aynu2mTPmY9RZOUOXlqWecFIILFXr70vv16/xCZnFxvri9IKkF1IGxQ8r9dOE62qGNpKxXx8Lko8bg=="], + + "brace-expansion": ["brace-expansion@1.1.13", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w=="], + + "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], + + "browserslist": ["browserslist@4.28.1", "", { "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", "electron-to-chromium": "^1.5.263", "node-releases": "^2.0.27", "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" } }, "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA=="], + + "buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="], + + "buffer-alloc": ["buffer-alloc@1.2.0", "", { "dependencies": { "buffer-alloc-unsafe": "^1.1.0", "buffer-fill": "^1.0.0" } }, "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow=="], + + "buffer-alloc-unsafe": ["buffer-alloc-unsafe@1.1.0", "", {}, "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg=="], + + "buffer-equal": ["buffer-equal@0.0.1", "", {}, "sha512-RgSV6InVQ9ODPdLWJ5UAqBqJBOg370Nz6ZQtRzpt6nUjc8v0St97uJ4PYC6NztqIScrAXafKM3mZPMygSe1ggA=="], + + "buffer-fill": ["buffer-fill@1.0.0", "", {}, "sha512-T7zexNBwiiaCOGDg9xNX9PBmjrubblRkENuptryuI64URkXDFum9il/JGL8Lm8wYfAXpredVXXZz7eMHilimiQ=="], + + "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], + + "cacheable": ["cacheable@2.3.4", "", { "dependencies": { "@cacheable/memory": "^2.0.8", "@cacheable/utils": "^2.4.0", "hookified": "^1.15.0", "keyv": "^5.6.0", "qified": "^0.9.0" } }, "sha512-djgxybDbw9fL/ZWMI3+CE8ZilNxcwFkVtDc1gJ+IlOSSWkSMPQabhV/XCHTQ6pwwN6aivXPZ43omTooZiX06Ew=="], + + "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], + + "caniuse-lite": ["caniuse-lite@1.0.30001782", "", {}, "sha512-dZcaJLJeDMh4rELYFw1tvSn1bhZWYFOt468FcbHHxx/Z/dFidd1I6ciyFdi3iwfQCyOjqo9upF6lGQYtMiJWxw=="], + + "caseless": ["caseless@0.12.0", "", {}, "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw=="], + + "centra": ["centra@2.7.0", "", { "dependencies": { "follow-redirects": "^1.15.6" } }, "sha512-PbFMgMSrmgx6uxCdm57RUos9Tc3fclMvhLSATYN39XsDV29B89zZ3KA89jmY0vwSGazyU+uerqwa6t+KaodPcg=="], + + "chai": ["chai@6.2.2", "", {}, "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg=="], + + "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], + + "chance": ["chance@1.1.3", "", {}, "sha512-XeJsdoVAzDb1WRPRuMBesRSiWpW1uNTo5Fd7mYxPJsAfgX71+jfuCOHOdbyBz2uAUZ8TwKcXgWk3DMedFfJkbg=="], + + "chardet": ["chardet@0.4.2", "", {}, "sha512-j/Toj7f1z98Hh2cYo2BVr85EpIRWqUi7rtRSGxh/cqUjqrnJe9l9UE7IUGd2vQ2p+kSHLkSzObQPZPLUC6TQwg=="], + + "charenc": ["charenc@0.0.2", "", {}, "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA=="], + + "chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], + + "chownr": ["chownr@1.1.4", "", {}, "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="], + + "class-validator": ["class-validator@0.11.0", "", { "dependencies": { "@types/validator": "10.11.3", "google-libphonenumber": "^3.1.6", "validator": "12.0.0" } }, "sha512-niAmmSPFku9xsnpYYrddy8NZRrCX3yyoZ/rgPKOilE5BG0Ma1eVCIxpR4X0LasL/6BzbYzsutG+mSbAXlh4zNw=="], + + "cli-cursor": ["cli-cursor@2.1.0", "", { "dependencies": { "restore-cursor": "^2.0.0" } }, "sha512-8lgKz8LmCRYZZQDpRyT2m5rKJ08TnU4tR9FFFW2rxpxR1FzWi4PQ/NfyODchAatHaUgnSPVcx/R5w6NuTBzFiw=="], + + "cli-width": ["cli-width@2.2.1", "", {}, "sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw=="], + + "clone": ["clone@2.1.2", "", {}, "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w=="], + + "clone-buffer": ["clone-buffer@1.0.0", "", {}, "sha512-KLLTJWrvwIP+OPfMn0x2PheDEP20RPUcGXj/ERegTgdmPEZylALQldygiqrPPu8P45uNuPs7ckmReLY6v/iA5g=="], + + "clone-stats": ["clone-stats@1.0.0", "", {}, "sha512-au6ydSpg6nsrigcZ4m8Bc9hxjeW+GJ8xh5G3BJCMt4WXe1H10UNaVOamqQTmrx1kjVuxAHIQSNU6hY4Nsn9/ag=="], + + "cloneable-readable": ["cloneable-readable@1.1.3", "", { "dependencies": { "inherits": "^2.0.1", "process-nextick-args": "^2.0.0", "readable-stream": "^2.3.5" } }, "sha512-2EF8zTQOxYq70Y4XKtorQupqF0m49MBz2/yf5Bj+MHjvpG3Hy7sImifnqD6UA+TKYxeSV+u6qqQPawN5UvnpKQ=="], + + "code-point-at": ["code-point-at@1.1.0", "", {}, "sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA=="], + + "color": ["color@3.2.1", "", { "dependencies": { "color-convert": "^1.9.3", "color-string": "^1.6.0" } }, "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA=="], + + "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], + + "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + + "color-string": ["color-string@1.9.1", "", { "dependencies": { "color-name": "^1.0.0", "simple-swizzle": "^0.2.2" } }, "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg=="], + + "colord": ["colord@2.9.3", "", {}, "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw=="], + + "colors": ["colors@1.4.0", "", {}, "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA=="], + + "combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="], + + "commander": ["commander@4.0.1", "", {}, "sha512-IPF4ouhCP+qdlcmCedhxX4xiGBPyigb8v5NeUp+0LyhwLgxMqyp3S0vl7TAPfS/hiP7FC3caI/PB9lTmP8r1NA=="], + + "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], + + "console-control-strings": ["console-control-strings@1.1.0", "", {}, "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ=="], + + "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], + + "core-js": ["core-js@2.6.12", "", {}, "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ=="], + + "core-util-is": ["core-util-is@1.0.3", "", {}, "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="], + + "cosmiconfig": ["cosmiconfig@9.0.1", "", { "dependencies": { "env-paths": "^2.2.1", "import-fresh": "^3.3.0", "js-yaml": "^4.1.0", "parse-json": "^5.2.0" }, "peerDependencies": { "typescript": ">=4.9.5" }, "optionalPeers": ["typescript"] }, "sha512-hr4ihw+DBqcvrsEDioRO31Z17x71pUYoNe/4h6Z0wB72p7MU7/9gH8Q3s12NFhHPfYBBOV3qyfUxmr/Yn3shnQ=="], + + "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], + + "crypt": ["crypt@0.0.2", "", {}, "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow=="], + + "css-functions-list": ["css-functions-list@3.3.3", "", {}, "sha512-8HFEBPKhOpJPEPu70wJJetjKta86Gw9+CCyCnB3sui2qQfOvRyqBy4IKLKKAwdMpWb2lHXWk9Wb4Z6AmaUT1Pg=="], + + "css-tree": ["css-tree@3.2.1", "", { "dependencies": { "mdn-data": "2.27.1", "source-map-js": "^1.2.1" } }, "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA=="], + + "cssesc": ["cssesc@3.0.0", "", { "bin": { "cssesc": "bin/cssesc" } }, "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="], + + "dashdash": ["dashdash@1.14.1", "", { "dependencies": { "assert-plus": "^1.0.0" } }, "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g=="], + + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "decompress-response": ["decompress-response@4.2.1", "", { "dependencies": { "mimic-response": "^2.0.0" } }, "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw=="], + + "deep-extend": ["deep-extend@0.6.0", "", {}, "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="], + + "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], + + "delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="], + + "delegates": ["delegates@1.0.0", "", {}, "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ=="], + + "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], + + "dir-glob": ["dir-glob@3.0.1", "", { "dependencies": { "path-type": "^4.0.0" } }, "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA=="], + + "dom-walk": ["dom-walk@0.1.2", "", {}, "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w=="], + + "ecc-jsbn": ["ecc-jsbn@0.1.2", "", { "dependencies": { "jsbn": "~0.1.0", "safer-buffer": "^2.1.0" } }, "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw=="], + + "electron-to-chromium": ["electron-to-chromium@1.5.328", "", {}, "sha512-QNQ5l45DzYytThO21403XN3FvK0hOkWDG8viNf6jqS42msJ8I4tGDSpBCgvDRRPnkffafiwAym2X2eHeGD2V0w=="], + + "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "encoding": ["encoding@0.1.13", "", { "dependencies": { "iconv-lite": "^0.6.2" } }, "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A=="], + + "end-of-stream": ["end-of-stream@1.4.5", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg=="], + + "enhanced-resolve": ["enhanced-resolve@5.20.1", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.0" } }, "sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA=="], + + "entities": ["entities@7.0.1", "", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="], + + "env-paths": ["env-paths@2.2.1", "", {}, "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A=="], + + "error-ex": ["error-ex@1.3.4", "", { "dependencies": { "is-arrayish": "^0.2.1" } }, "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ=="], + + "es-module-lexer": ["es-module-lexer@2.0.0", "", {}, "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw=="], + + "es6-promise": ["es6-promise@3.3.1", "", {}, "sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg=="], + + "esbuild": ["esbuild@0.27.4", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.4", "@esbuild/android-arm": "0.27.4", "@esbuild/android-arm64": "0.27.4", "@esbuild/android-x64": "0.27.4", "@esbuild/darwin-arm64": "0.27.4", "@esbuild/darwin-x64": "0.27.4", "@esbuild/freebsd-arm64": "0.27.4", "@esbuild/freebsd-x64": "0.27.4", "@esbuild/linux-arm": "0.27.4", "@esbuild/linux-arm64": "0.27.4", "@esbuild/linux-ia32": "0.27.4", "@esbuild/linux-loong64": "0.27.4", "@esbuild/linux-mips64el": "0.27.4", "@esbuild/linux-ppc64": "0.27.4", "@esbuild/linux-riscv64": "0.27.4", "@esbuild/linux-s390x": "0.27.4", "@esbuild/linux-x64": "0.27.4", "@esbuild/netbsd-arm64": "0.27.4", "@esbuild/netbsd-x64": "0.27.4", "@esbuild/openbsd-arm64": "0.27.4", "@esbuild/openbsd-x64": "0.27.4", "@esbuild/openharmony-arm64": "0.27.4", "@esbuild/sunos-x64": "0.27.4", "@esbuild/win32-arm64": "0.27.4", "@esbuild/win32-ia32": "0.27.4", "@esbuild/win32-x64": "0.27.4" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ=="], + + "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], + + "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], + + "escodegen-wallaby": ["escodegen-wallaby@1.6.27", "", { "dependencies": { "esprima": "^2.7.1", "estraverse": "^1.9.1", "esutils": "^2.0.2", "optionator": "^0.8.1" }, "optionalDependencies": { "source-map": "~0.2.0" }, "bin": { "escodegen": "./bin/escodegen.js", "esgenerate": "./bin/esgenerate.js" } }, "sha512-DFXbfLTNa/wiQRgqSseKhmzyDhwWJ4c/6IwsdAgA6qYL0ntsmSuuTMovEbA/8oCanSGkUR1qaLsDgKiWPllnjg=="], + + "eslint": ["eslint@9.39.4", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.2", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.5", "@eslint/js": "9.39.4", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.5", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ=="], + + "eslint-scope": ["eslint-scope@8.4.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg=="], + + "eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="], + + "espree": ["espree@10.4.0", "", { "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.1" } }, "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ=="], + + "esprima": ["esprima@2.7.3", "", { "bin": { "esparse": "./bin/esparse.js", "esvalidate": "./bin/esvalidate.js" } }, "sha512-OarPfz0lFCiW4/AV2Oy1Rp9qu0iusTKqykwTspGCZtPxmF81JR4MmIebvF1F9+UOKth2ZubLQ4XGGaU+hSn99A=="], + + "esquery": ["esquery@1.7.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g=="], + + "esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="], + + "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], + + "estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], + + "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], + + "event-target-shim": ["event-target-shim@5.0.1", "", {}, "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="], + + "eventemitter3": ["eventemitter3@4.0.0", "", {}, "sha512-qerSRB0p+UDEssxTtm6EDKcE7W4OaoisfIMl4CngyEhjpYglocpNg6UEqCvemdGhosAsg4sO2dXJOdyBifPGCg=="], + + "events": ["events@3.3.0", "", {}, "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="], + + "exif-parser": ["exif-parser@0.1.12", "", {}, "sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw=="], + + "expand-template": ["expand-template@2.0.3", "", {}, "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg=="], + + "expect-type": ["expect-type@1.3.0", "", {}, "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA=="], + + "extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="], + + "external-editor": ["external-editor@2.2.0", "", { "dependencies": { "chardet": "^0.4.0", "iconv-lite": "^0.4.17", "tmp": "^0.0.33" } }, "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A=="], + + "extsprintf": ["extsprintf@1.3.0", "", {}, "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g=="], + + "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], + + "fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="], + + "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="], + + "fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="], + + "fast-uri": ["fast-uri@3.1.0", "", {}, "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA=="], + + "fastest-levenshtein": ["fastest-levenshtein@1.0.16", "", {}, "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg=="], + + "fastq": ["fastq@1.20.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw=="], + + "favicons": ["favicons@6.2.2", "", { "dependencies": { "clone": "^2.1.2", "colors": "^1.4.0", "image-size": "^0.8.3", "jimp": "^0.16.1", "jsontoxml": "^1.0.1", "lodash.defaultsdeep": "^4.6.1", "require-directory": "^2.1.1", "sharp": "^0.28.2", "through2": "^4.0.2", "tinycolor2": "^1.4.2", "to-ico": "^1.1.5", "vinyl": "^2.2.1", "xml2js": "^0.4.23" } }, "sha512-qhvFqbhlXA/JYIDYuxTrE4uT9rcpTCrWvF3UG0GxBoLl/XgFBBTrZkQvASrkMebSwDCJ9kKGypRWIbvoRZLBsw=="], + + "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], + + "figures": ["figures@2.0.0", "", { "dependencies": { "escape-string-regexp": "^1.0.5" } }, "sha512-Oa2M9atig69ZkfwiApY8F2Yy+tzMbazyvqv21R0NsSC8floSOC09BbT1ITWAdoMGQvJ/aZnR1KMwdx9tvHnTNA=="], + + "file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="], + + "file-type": ["file-type@3.9.0", "", {}, "sha512-RLoqTXE8/vPmMuTI88DAzhMYC99I8BWv7zYP4A1puo5HIjEJ5EX48ighy4ZyKMG9EDXxBgW6e++cn7d1xuFghA=="], + + "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], + + "find-root": ["find-root@1.1.0", "", {}, "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng=="], + + "find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="], + + "flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="], + + "flatted": ["flatted@3.4.2", "", {}, "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA=="], + + "follow-redirects": ["follow-redirects@1.15.11", "", {}, "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ=="], + + "forever-agent": ["forever-agent@0.6.1", "", {}, "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw=="], + + "form-data": ["form-data@2.3.3", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.6", "mime-types": "^2.1.12" } }, "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ=="], + + "fraction.js": ["fraction.js@5.3.4", "", {}, "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ=="], + + "fs-constants": ["fs-constants@1.0.0", "", {}, "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="], + + "fs-extra": ["fs-extra@10.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ=="], + + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + + "gauge": ["gauge@2.7.4", "", { "dependencies": { "aproba": "^1.0.3", "console-control-strings": "^1.0.0", "has-unicode": "^2.0.0", "object-assign": "^4.1.0", "signal-exit": "^3.0.0", "string-width": "^1.0.1", "strip-ansi": "^3.0.1", "wide-align": "^1.1.0" } }, "sha512-14x4kjc6lkD3ltw589k0NrPD6cCNTD6CWoVUNpB85+DrtONoZn+Rug6xZU5RvSC4+TZPxA5AnBibQYAvZn41Hg=="], + + "get-stream": ["get-stream@2.3.1", "", { "dependencies": { "object-assign": "^4.0.1", "pinkie-promise": "^2.0.0" } }, "sha512-AUGhbbemXxrZJRD5cDvKtQxLuYaIbNtDTK8YqupCI393Q2KSTreEsLUN3ZxAWFGiKTzL6nKuzfcIvieflUX9qA=="], + + "getpass": ["getpass@0.1.7", "", { "dependencies": { "assert-plus": "^1.0.0" } }, "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng=="], + + "gifwrap": ["gifwrap@0.9.4", "", { "dependencies": { "image-q": "^4.0.0", "omggif": "^1.0.10" } }, "sha512-MDMwbhASQuVeD4JKd1fKgNgCRL3fGqMM4WaqpNhWO0JiMOAjbQdumbs4BbBZEy9/M00EHEjKN3HieVhCUlwjeQ=="], + + "github-from-package": ["github-from-package@0.0.0", "", {}, "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw=="], + + "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], + + "global": ["global@4.4.0", "", { "dependencies": { "min-document": "^2.19.0", "process": "^0.11.10" } }, "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w=="], + + "global-modules": ["global-modules@2.0.0", "", { "dependencies": { "global-prefix": "^3.0.0" } }, "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A=="], + + "global-prefix": ["global-prefix@3.0.0", "", { "dependencies": { "ini": "^1.3.5", "kind-of": "^6.0.2", "which": "^1.3.1" } }, "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg=="], + + "globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="], + + "globby": ["globby@11.1.0", "", { "dependencies": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", "fast-glob": "^3.2.9", "ignore": "^5.2.0", "merge2": "^1.4.1", "slash": "^3.0.0" } }, "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g=="], + + "globjoin": ["globjoin@0.1.4", "", {}, "sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg=="], + + "google-libphonenumber": ["google-libphonenumber@3.2.44", "", {}, "sha512-9p2TghluF2LTChFMLWsDRD5N78SZDsILdUk4gyqYxBXluCyxoPiOq+Fqt7DKM+LUd33+OgRkdrc+cPR93AypCQ=="], + + "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], + + "happy-dom": ["happy-dom@20.8.9", "", { "dependencies": { "@types/node": ">=20.0.0", "@types/whatwg-mimetype": "^3.0.2", "@types/ws": "^8.18.1", "entities": "^7.0.1", "whatwg-mimetype": "^3.0.0", "ws": "^8.18.3" } }, "sha512-Tz23LR9T9jOGVZm2x1EPdXqwA37G/owYMxRwU0E4miurAtFsPMQ1d2Jc2okUaSjZqAFz2oEn3FLXC5a0a+siyA=="], + + "har-schema": ["har-schema@2.0.0", "", {}, "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q=="], + + "har-validator": ["har-validator@5.1.5", "", { "dependencies": { "ajv": "^6.12.3", "har-schema": "^2.0.0" } }, "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w=="], + + "has-ansi": ["has-ansi@2.0.0", "", { "dependencies": { "ansi-regex": "^2.0.0" } }, "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg=="], + + "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], + + "has-unicode": ["has-unicode@2.0.1", "", {}, "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ=="], + + "hashery": ["hashery@1.5.1", "", { "dependencies": { "hookified": "^1.15.0" } }, "sha512-iZyKG96/JwPz1N55vj2Ie2vXbhu440zfUfJvSwEqEbeLluk7NnapfGqa7LH0mOsnDxTF85Mx8/dyR6HfqcbmbQ=="], + + "hookified": ["hookified@1.15.1", "", {}, "sha512-MvG/clsADq1GPM2KGo2nyfaWVyn9naPiXrqIe4jYjXNZQt238kWyOGrsyc/DmRAQ+Re6yeo6yX/yoNCG5KAEVg=="], + + "html-escaper": ["html-escaper@2.0.2", "", {}, "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg=="], + + "html-tags": ["html-tags@3.3.1", "", {}, "sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ=="], + + "http-signature": ["http-signature@1.2.0", "", { "dependencies": { "assert-plus": "^1.0.0", "jsprim": "^1.2.2", "sshpk": "^1.7.0" } }, "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ=="], + + "iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="], + + "idb": ["idb@8.0.3", "", {}, "sha512-LtwtVyVYO5BqRvcsKuB2iUMnHwPVByPCXFXOpuU96IZPPoPN6xjOGxZQ74pgSVVLQWtUOYgyeL4GE98BY5D3wg=="], + + "idb-keyval": ["idb-keyval@6.2.2", "", {}, "sha512-yjD9nARJ/jb1g+CvD0tlhUHOrJ9Sy0P8T9MF3YaLlHnSRpwPfpTX0XIvpmw3gAJUmEu3FiICLBDPXVwyEvrleg=="], + + "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], + + "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], + + "image-q": ["image-q@4.0.0", "", { "dependencies": { "@types/node": "16.9.1" } }, "sha512-PfJGVgIfKQJuq3s0tTDOKtztksibuUEbJQIYT3by6wctQo+Rdlh7ef4evJ5NCdxY4CfMbvFkocEwbl4BF8RlJw=="], + + "image-size": ["image-size@0.8.3", "", { "dependencies": { "queue": "6.0.1" }, "bin": { "image-size": "bin/image-size.js" } }, "sha512-SMtq1AJ+aqHB45c3FsB4ERK0UCiA2d3H1uq8s+8T0Pf8A3W4teyBQyaFaktH6xvZqh+npwlKU7i4fJo0r7TYTg=="], + + "immutable": ["immutable@5.1.5", "", {}, "sha512-t7xcm2siw+hlUM68I+UEOK+z84RzmN59as9DZ7P1l0994DKUWV7UXBMQZVxaoMSRQ+PBZbHCOoBt7a2wxOMt+A=="], + + "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], + + "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], + + "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], + + "ini": ["ini@1.3.8", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="], + + "inquirer": ["inquirer@3.0.6", "", { "dependencies": { "ansi-escapes": "^1.1.0", "chalk": "^1.0.0", "cli-cursor": "^2.1.0", "cli-width": "^2.0.0", "external-editor": "^2.0.1", "figures": "^2.0.0", "lodash": "^4.3.0", "mute-stream": "0.0.7", "run-async": "^2.2.0", "rx": "^4.1.0", "string-width": "^2.0.0", "strip-ansi": "^3.0.0", "through": "^2.3.6" } }, "sha512-thluxTGBXUGb8DuQcvH9/CM/CrcGyB5xUpWc9x6Slqcq1z/hRr2a6KxUpX4ddRfmbe0hg3E4jTvo5833aWz3BA=="], + + "inversify": ["inversify@5.0.1", "", {}, "sha512-Ieh06s48WnEYGcqHepdsJUIJUXpwH5o5vodAX+DK2JA/gjy4EbEcQZxw+uFfzysmKjiLXGYwNG3qDZsKVMcINQ=="], + + "ip-regex": ["ip-regex@1.0.3", "", {}, "sha512-HjpCHTuxbR/6jWJroc/VN+npo5j0T4Vv2TAI5qdEHQx7hsL767MeccGFSsLtF694EiZKTSEqgoeU6DtGFCcuqQ=="], + + "is-arrayish": ["is-arrayish@0.2.1", "", {}, "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="], + + "is-buffer": ["is-buffer@1.1.6", "", {}, "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w=="], + + "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], + + "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], + + "is-function": ["is-function@1.0.2", "", {}, "sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ=="], + + "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], + + "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], + + "is-plain-object": ["is-plain-object@5.0.0", "", {}, "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q=="], + + "is-stream": ["is-stream@1.1.0", "", {}, "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ=="], + + "is-typedarray": ["is-typedarray@1.0.0", "", {}, "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA=="], + + "isarray": ["isarray@1.0.0", "", {}, "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="], + + "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + + "isstream": ["isstream@0.1.2", "", {}, "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g=="], + + "istanbul-lib-coverage": ["istanbul-lib-coverage@3.2.2", "", {}, "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg=="], + + "istanbul-lib-report": ["istanbul-lib-report@3.0.1", "", { "dependencies": { "istanbul-lib-coverage": "^3.0.0", "make-dir": "^4.0.0", "supports-color": "^7.1.0" } }, "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw=="], + + "istanbul-reports": ["istanbul-reports@3.2.0", "", { "dependencies": { "html-escaper": "^2.0.0", "istanbul-lib-report": "^3.0.0" } }, "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA=="], + + "javascript-obfuscator": ["javascript-obfuscator@0.18.8", "", { "dependencies": { "@babel/runtime": "7.7.4", "chalk": "3.0.0", "chance": "1.1.3", "class-validator": "0.11.0", "commander": "4.0.1", "escodegen-wallaby": "1.6.27", "espree": "^6.1.2", "estraverse": "4.3.0", "eventemitter3": "4.0.0", "inversify": "5.0.1", "js-string-escape": "1.0.1", "md5": "2.2.1", "mkdirp": "0.5.1", "multimatch": "4.0.0", "opencollective": "1.0.3", "reflect-metadata": "0.1.13", "source-map-support": "0.5.16", "string-template": "1.0.0", "tslib": "1.10.0" }, "bin": { "javascript-obfuscator": "bin/javascript-obfuscator" } }, "sha512-lh/PT3dwLgv2vP9ymsmNZqjpbkDq1SQqpxLYtAerLzmYgHlQOzVpUe3NbFb8tUFNylMeg6ez1iEpA7oOQw/7Tw=="], + + "jimp": ["jimp@0.16.13", "", { "dependencies": { "@babel/runtime": "^7.7.2", "@jimp/custom": "^0.16.13", "@jimp/plugins": "^0.16.13", "@jimp/types": "^0.16.13", "regenerator-runtime": "^0.13.3" } }, "sha512-Bxz8q7V4rnCky9A0ktTNGA9SkNFVWRHodddI/DaAWZJzF7sVUlFYKQ60y9JGqrKpi48ECA/TnfMzzc5C70VByA=="], + + "jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], + + "jpeg-js": ["jpeg-js@0.1.2", "", {}, "sha512-CiRVjMKBUp6VYtGwzRjrdnro41yMwKGOWdP9ATXqmixdz2n7KHNwdqthTYSSbOKj/Ha79Gct1sA8ZQpse55TYA=="], + + "js-string-escape": ["js-string-escape@1.0.1", "", {}, "sha512-Smw4xcfIQ5LVjAOuJCvN/zIodzA/BBSsluuoSykP+lUvScIi4U6RJLfwHet5cxFnCswUjISV8oAXaqaJDY3chg=="], + + "js-tokens": ["js-tokens@10.0.0", "", {}, "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q=="], + + "js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], + + "jsbn": ["jsbn@0.1.1", "", {}, "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg=="], + + "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="], + + "json-parse-even-better-errors": ["json-parse-even-better-errors@2.3.1", "", {}, "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="], + + "json-schema": ["json-schema@0.4.0", "", {}, "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="], + + "json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], + + "json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="], + + "json-stringify-safe": ["json-stringify-safe@5.0.1", "", {}, "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA=="], + + "jsonfile": ["jsonfile@6.2.0", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg=="], + + "jsontoxml": ["jsontoxml@1.0.1", "", {}, "sha512-dtKGq0K8EWQBRqcAaePSgKR4Hyjfsz/LkurHSV3Cxk4H+h2fWDeaN2jzABz+ZmOJylgXS7FGeWmbZ6jgYUMdJQ=="], + + "jsprim": ["jsprim@1.4.2", "", { "dependencies": { "assert-plus": "1.0.0", "extsprintf": "1.3.0", "json-schema": "0.4.0", "verror": "1.10.0" } }, "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw=="], + + "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], + + "kind-of": ["kind-of@6.0.3", "", {}, "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw=="], + + "known-css-properties": ["known-css-properties@0.37.0", "", {}, "sha512-JCDrsP4Z1Sb9JwG0aJ8Eo2r7k4Ou5MwmThS/6lcIe1ICyb7UBJKGRIUUdqc2ASdE/42lgz6zFUnzAIhtXnBVrQ=="], + + "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], + + "lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="], + + "lightningcss-android-arm64": ["lightningcss-android-arm64@1.32.0", "", { "os": "android", "cpu": "arm64" }, "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg=="], + + "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.32.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ=="], + + "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.32.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w=="], + + "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.32.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig=="], + + "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.32.0", "", { "os": "linux", "cpu": "arm" }, "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw=="], + + "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ=="], + + "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg=="], + + "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA=="], + + "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg=="], + + "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.32.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw=="], + + "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="], + + "lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="], + + "load-bmfont": ["load-bmfont@1.4.2", "", { "dependencies": { "buffer-equal": "0.0.1", "mime": "^1.3.4", "parse-bmfont-ascii": "^1.0.3", "parse-bmfont-binary": "^1.0.5", "parse-bmfont-xml": "^1.1.4", "phin": "^3.7.1", "xhr": "^2.0.1", "xtend": "^4.0.0" } }, "sha512-qElWkmjW9Oq1F9EI5Gt7aD9zcdHb9spJCW1L/dmPf7KzCCEJxq8nhHz5eCgI9aMf7vrG/wyaCqdsI+Iy9ZTlog=="], + + "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], + + "lodash": ["lodash@4.17.23", "", {}, "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w=="], + + "lodash.defaultsdeep": ["lodash.defaultsdeep@4.6.1", "", {}, "sha512-3j8wdDzYuWO3lM3Reg03MuQR957t287Rpcxp1njpEa8oDrikb+FwGdW3n+FELh/A6qib6yPit0j/pv9G/yeAqA=="], + + "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="], + + "lodash.truncate": ["lodash.truncate@4.4.2", "", {}, "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw=="], + + "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], + + "magicast": ["magicast@0.5.2", "", { "dependencies": { "@babel/parser": "^7.29.0", "@babel/types": "^7.29.0", "source-map-js": "^1.2.1" } }, "sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ=="], + + "make-dir": ["make-dir@4.0.0", "", { "dependencies": { "semver": "^7.5.3" } }, "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw=="], + + "mathml-tag-names": ["mathml-tag-names@2.1.3", "", {}, "sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg=="], + + "md5": ["md5@2.2.1", "", { "dependencies": { "charenc": "~0.0.1", "crypt": "~0.0.1", "is-buffer": "~1.1.1" } }, "sha512-PlGG4z5mBANDGCKsYQe0CaUYHdZYZt8ZPZLmEt+Urf0W4GlpTX4HescwHU+dc9+Z/G/vZKYZYFrwgm9VxK6QOQ=="], + + "mdn-data": ["mdn-data@2.27.1", "", {}, "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ=="], + + "meow": ["meow@13.2.0", "", {}, "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA=="], + + "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="], + + "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], + + "mime": ["mime@1.6.0", "", { "bin": { "mime": "cli.js" } }, "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="], + + "mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], + + "mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], + + "mimic-fn": ["mimic-fn@1.2.0", "", {}, "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ=="], + + "mimic-response": ["mimic-response@2.1.0", "", {}, "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA=="], + + "min-document": ["min-document@2.19.2", "", { "dependencies": { "dom-walk": "^0.1.0" } }, "sha512-8S5I8db/uZN8r9HSLFVWPdJCvYOejMcEC82VIzNUc6Zkklf/d1gg2psfE79/vyhWOj4+J8MtwmoOz3TmvaGu5A=="], + + "minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], + + "minimist": ["minimist@0.0.8", "", {}, "sha512-miQKw5Hv4NS1Psg2517mV4e4dYNaO3++hjAvLOAzKqZ61rH8NS1SK+vbfBWZ5PY/Me/bEWhUwqMghEW5Fb9T7Q=="], + + "mkdirp": ["mkdirp@0.5.1", "", { "dependencies": { "minimist": "0.0.8" }, "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-SknJC52obPfGQPnjIkXbmA6+5H15E+fR+E4iR2oQ3zzCLbd7/ONua69R/Gw7AgkTLsRG+r5fzksYwWe1AgTyWA=="], + + "mkdirp-classic": ["mkdirp-classic@0.5.3", "", {}, "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "multimatch": ["multimatch@4.0.0", "", { "dependencies": { "@types/minimatch": "^3.0.3", "array-differ": "^3.0.0", "array-union": "^2.1.0", "arrify": "^2.0.1", "minimatch": "^3.0.4" } }, "sha512-lDmx79y1z6i7RNx0ZGCPq1bzJ6ZoDDKbvh7jxr9SJcWLkShMzXrHbYVpTdnhNM5MXpDUxCQ4DgqVttVXlBgiBQ=="], + + "mute-stream": ["mute-stream@0.0.7", "", {}, "sha512-r65nCZhrbXXb6dXOACihYApHw2Q6pV0M3V0PSxd74N0+D8nzAdEAITq2oAjA1jVnKI+tGvEBUpqiMh0+rW6zDQ=="], + + "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], + + "napi-build-utils": ["napi-build-utils@1.0.2", "", {}, "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg=="], + + "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], + + "node-abi": ["node-abi@2.30.1", "", { "dependencies": { "semver": "^5.4.1" } }, "sha512-/2D0wOQPgaUWzVSVgRMx+trKJRC2UG4SUc4oCJoXx9Uxjtp0Vy3/kt7zcbxHF8+Z/pK3UloLWzBISg72brfy1w=="], + + "node-addon-api": ["node-addon-api@7.1.1", "", {}, "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ=="], + + "node-fetch": ["node-fetch@1.6.3", "", { "dependencies": { "encoding": "^0.1.11", "is-stream": "^1.0.1" } }, "sha512-BDxbhLHXFFFvilHjh9xihcDyPkXQ+kjblxnl82zAX41xUYSNvuRpFRznmldR9+OKu+p+ULZ7hNoyunlLB5ecUA=="], + + "node-releases": ["node-releases@2.0.36", "", {}, "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA=="], + + "normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="], + + "npmlog": ["npmlog@4.1.2", "", { "dependencies": { "are-we-there-yet": "~1.1.2", "console-control-strings": "~1.1.0", "gauge": "~2.7.3", "set-blocking": "~2.0.0" } }, "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg=="], + + "number-is-nan": ["number-is-nan@1.0.1", "", {}, "sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ=="], + + "oauth-sign": ["oauth-sign@0.9.0", "", {}, "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ=="], + + "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], + + "obug": ["obug@2.1.1", "", {}, "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ=="], + + "omggif": ["omggif@1.0.10", "", {}, "sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw=="], + + "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], + + "onetime": ["onetime@2.0.1", "", { "dependencies": { "mimic-fn": "^1.0.0" } }, "sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ=="], + + "opencollective": ["opencollective@1.0.3", "", { "dependencies": { "babel-polyfill": "6.23.0", "chalk": "1.1.3", "inquirer": "3.0.6", "minimist": "1.2.0", "node-fetch": "1.6.3", "opn": "4.0.2" }, "bin": { "opencollective": "./dist/bin/opencollective.js", "oc": "./dist/bin/opencollective.js" } }, "sha512-YBRI0Qa8+Ui0/STV1qYuPrJm889PT3oCPHMVoL+8Y3nwCffj7PSrB2NlGgrhgBKDujxTjxknHWJ/FiqOsYcIDw=="], + + "opn": ["opn@4.0.2", "", { "dependencies": { "object-assign": "^4.0.1", "pinkie-promise": "^2.0.0" } }, "sha512-iPBWbPP4OEOzR1xfhpGLDh+ypKBOygunZhM9jBtA7FS5sKjEiMZw0EFb82hnDOmTZX90ZWLoZKUza4cVt8MexA=="], + + "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], + + "os-tmpdir": ["os-tmpdir@1.0.2", "", {}, "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g=="], + + "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], + + "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], + + "pako": ["pako@1.0.11", "", {}, "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="], + + "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], + + "parse-author": ["parse-author@2.0.0", "", { "dependencies": { "author-regex": "^1.0.0" } }, "sha512-yx5DfvkN8JsHL2xk2Os9oTia467qnvRgey4ahSm2X8epehBLx/gWLcy5KI+Y36ful5DzGbCS6RazqZGgy1gHNw=="], + + "parse-bmfont-ascii": ["parse-bmfont-ascii@1.0.6", "", {}, "sha512-U4RrVsUFCleIOBsIGYOMKjn9PavsGOXxbvYGtMOEfnId0SVNsgehXh1DxUdVPLoxd5mvcEtvmKs2Mmf0Mpa1ZA=="], + + "parse-bmfont-binary": ["parse-bmfont-binary@1.0.6", "", {}, "sha512-GxmsRea0wdGdYthjuUeWTMWPqm2+FAd4GI8vCvhgJsFnoGhTrLhXDDupwTo7rXVAgaLIGoVHDZS9p/5XbSqeWA=="], + + "parse-bmfont-xml": ["parse-bmfont-xml@1.1.6", "", { "dependencies": { "xml-parse-from-string": "^1.0.0", "xml2js": "^0.5.0" } }, "sha512-0cEliVMZEhrFDwMh4SxIyVJpqYoOWDJ9P895tFuS+XuNzI5UBmBk5U5O4KuJdTnZpSBI4LFA2+ZiJaiwfSwlMA=="], + + "parse-headers": ["parse-headers@2.0.6", "", {}, "sha512-Tz11t3uKztEW5FEVZnj1ox8GKblWn+PvHY9TmJV5Mll2uHEwRdR/5Li1OlXoECjLYkApdhWy44ocONwXLiKO5A=="], + + "parse-json": ["parse-json@5.2.0", "", { "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", "json-parse-even-better-errors": "^2.3.0", "lines-and-columns": "^1.1.6" } }, "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg=="], + + "parse-png": ["parse-png@1.1.2", "", { "dependencies": { "pngjs": "^3.2.0" } }, "sha512-Ge6gDV9T5zhkWHmjvnNiyhPTCIoY7W+FC7qWPtuL2lIGZAFxxqTRG/ouEXsH9qkw+HzYiPEU/tFcxOCEDTP1Yw=="], + + "parse5": ["parse5@6.0.1", "", {}, "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw=="], + + "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], + + "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], + + "path-type": ["path-type@4.0.0", "", {}, "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw=="], + + "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], + + "peek-readable": ["peek-readable@4.1.0", "", {}, "sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg=="], + + "performance-now": ["performance-now@2.1.0", "", {}, "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow=="], + + "phin": ["phin@2.9.3", "", {}, "sha512-CzFr90qM24ju5f88quFC/6qohjC144rehe5n6DH900lgXmUe86+xCKc10ev56gRKC4/BkHUoG4uSiQgBiIXwDA=="], + + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], + + "picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="], + + "pinkie": ["pinkie@2.0.4", "", {}, "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg=="], + + "pinkie-promise": ["pinkie-promise@2.0.1", "", { "dependencies": { "pinkie": "^2.0.0" } }, "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw=="], + + "pixelmatch": ["pixelmatch@4.0.2", "", { "dependencies": { "pngjs": "^3.0.0" }, "bin": { "pixelmatch": "bin/pixelmatch" } }, "sha512-J8B6xqiO37sU/gkcMglv6h5Jbd9xNER7aHzpfRdNmV4IbQBzBpe4l9XmbG+xPF/znacgu2jfEw+wHffaq/YkXA=="], + + "pngjs": ["pngjs@3.4.0", "", {}, "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w=="], + + "postcss": ["postcss@8.5.8", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg=="], + + "postcss-media-query-parser": ["postcss-media-query-parser@0.2.3", "", {}, "sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig=="], + + "postcss-resolve-nested-selector": ["postcss-resolve-nested-selector@0.1.6", "", {}, "sha512-0sglIs9Wmkzbr8lQwEyIzlDOOC9bGmfVKcJTaxv3vMmd3uo4o4DerC3En0bnmgceeql9BfC8hRkp7cg0fjdVqw=="], + + "postcss-safe-parser": ["postcss-safe-parser@7.0.1", "", { "peerDependencies": { "postcss": "^8.4.31" } }, "sha512-0AioNCJZ2DPYz5ABT6bddIqlhgwhpHZ/l65YAYo0BCIn0xiDpsnTHz0gnoTGk0OXZW0JRs+cDwL8u/teRdz+8A=="], + + "postcss-scss": ["postcss-scss@4.0.9", "", { "peerDependencies": { "postcss": "^8.4.29" } }, "sha512-AjKOeiwAitL/MXxQW2DliT28EKukvvbEWx3LBmJIRN8KfBGZbRTxNYW0kSqi1COiTZ57nZ9NW06S6ux//N1c9A=="], + + "postcss-selector-parser": ["postcss-selector-parser@7.1.1", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg=="], + + "postcss-value-parser": ["postcss-value-parser@4.2.0", "", {}, "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="], + + "prebuild-install": ["prebuild-install@6.1.4", "", { "dependencies": { "detect-libc": "^1.0.3", "expand-template": "^2.0.3", "github-from-package": "0.0.0", "minimist": "^1.2.3", "mkdirp-classic": "^0.5.3", "napi-build-utils": "^1.0.1", "node-abi": "^2.21.0", "npmlog": "^4.0.1", "pump": "^3.0.0", "rc": "^1.2.7", "simple-get": "^3.0.3", "tar-fs": "^2.0.0", "tunnel-agent": "^0.6.0" }, "bin": { "prebuild-install": "bin.js" } }, "sha512-Z4vpywnK1lBg+zdPCVCsKq0xO66eEV9rWo2zrROGGiRS4JtueBOdlB1FnY8lcy7JsUud/Q3ijUxyWN26Ika0vQ=="], + + "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], + + "process": ["process@0.11.10", "", {}, "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A=="], + + "process-nextick-args": ["process-nextick-args@2.0.1", "", {}, "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="], + + "psl": ["psl@1.15.0", "", { "dependencies": { "punycode": "^2.3.1" } }, "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w=="], + + "pump": ["pump@3.0.4", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA=="], + + "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], + + "qified": ["qified@0.9.0", "", { "dependencies": { "hookified": "^2.1.0" } }, "sha512-4q61YgkHbY6gmwkqm0BsxyLDO3UYdrdiJTJ7JiaZb3xpW1duxn135SB7KqUEkCiuu5O4W+TtwEWP2VjmSRanvA=="], + + "qs": ["qs@6.5.5", "", {}, "sha512-mzR4sElr1bfCaPJe7m8ilJ6ZXdDaGoObcYR0ZHSsktM/Lt21MVHj5De30GQH2eiZ1qGRTO7LCAzQsUeXTNexWQ=="], + + "queue": ["queue@6.0.1", "", { "dependencies": { "inherits": "~2.0.3" } }, "sha512-AJBQabRCCNr9ANq8v77RJEv73DPbn55cdTb+Giq4X0AVnNVZvMHlYp7XlQiN+1npCZj1DuSmaA2hYVUUDgxFDg=="], + + "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], + + "rc": ["rc@1.2.8", "", { "dependencies": { "deep-extend": "^0.6.0", "ini": "~1.3.0", "minimist": "^1.2.0", "strip-json-comments": "~2.0.1" }, "bin": { "rc": "./cli.js" } }, "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw=="], + + "read-chunk": ["read-chunk@1.0.1", "", {}, "sha512-5NLTTdX45dKFtG8CX5pKmvS9V5u9wBE+gkklN7xhDuhq3pA2I4O7ALfKxosCMcLHOhkxj6GNacZhfXtp5nlCdg=="], + + "readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], + + "readable-web-to-node-stream": ["readable-web-to-node-stream@3.0.4", "", { "dependencies": { "readable-stream": "^4.7.0" } }, "sha512-9nX56alTf5bwXQ3ZDipHJhusu9NTQJ/CVPtb/XHAJCXihZeitfJvIRS4GqQ/mfIoOE3IelHMrpayVrosdHBuLw=="], + + "readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="], + + "reflect-metadata": ["reflect-metadata@0.1.13", "", {}, "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg=="], + + "regenerator-runtime": ["regenerator-runtime@0.13.11", "", {}, "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="], + + "remove-trailing-separator": ["remove-trailing-separator@1.1.0", "", {}, "sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw=="], + + "replace-ext": ["replace-ext@1.0.1", "", {}, "sha512-yD5BHCe7quCgBph4rMQ+0KkIRKwWCrHDOX1p1Gp6HwjPM5kVoCdKGNhN7ydqqsX6lJEnQDKZ/tFMiEdQ1dvPEw=="], + + "request": ["request@2.88.2", "", { "dependencies": { "aws-sign2": "~0.7.0", "aws4": "^1.8.0", "caseless": "~0.12.0", "combined-stream": "~1.0.6", "extend": "~3.0.2", "forever-agent": "~0.6.1", "form-data": "~2.3.2", "har-validator": "~5.1.3", "http-signature": "~1.2.0", "is-typedarray": "~1.0.0", "isstream": "~0.1.2", "json-stringify-safe": "~5.0.1", "mime-types": "~2.1.19", "oauth-sign": "~0.9.0", "performance-now": "^2.1.0", "qs": "~6.5.2", "safe-buffer": "^5.1.2", "tough-cookie": "~2.5.0", "tunnel-agent": "^0.6.0", "uuid": "^3.3.2" } }, "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw=="], + + "require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="], + + "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="], + + "resize-img": ["resize-img@1.1.2", "", { "dependencies": { "bmp-js": "0.0.1", "file-type": "^3.8.0", "get-stream": "^2.0.0", "jimp": "^0.2.21", "jpeg-js": "^0.1.1", "parse-png": "^1.1.1" } }, "sha512-/4nKUmuNPuM6gYTWad136ica81baOVjpesgv8FGaIvP0KWcbCWahOWBKaM4tFoM+aVcSA+qQDg28pcnIzFRpJw=="], + + "resolve-from": ["resolve-from@5.0.0", "", {}, "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw=="], + + "restore-cursor": ["restore-cursor@2.0.0", "", { "dependencies": { "onetime": "^2.0.0", "signal-exit": "^3.0.2" } }, "sha512-6IzJLuGi4+R14vwagDHX+JrXmPVtPpn4mffDJ1UdR7/Edm87fl6yi8mMBIVvFtJaNTUvjughmW4hwLhRG7gC1Q=="], + + "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], + + "rollup": ["rollup@4.60.1", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.60.1", "@rollup/rollup-android-arm64": "4.60.1", "@rollup/rollup-darwin-arm64": "4.60.1", "@rollup/rollup-darwin-x64": "4.60.1", "@rollup/rollup-freebsd-arm64": "4.60.1", "@rollup/rollup-freebsd-x64": "4.60.1", "@rollup/rollup-linux-arm-gnueabihf": "4.60.1", "@rollup/rollup-linux-arm-musleabihf": "4.60.1", "@rollup/rollup-linux-arm64-gnu": "4.60.1", "@rollup/rollup-linux-arm64-musl": "4.60.1", "@rollup/rollup-linux-loong64-gnu": "4.60.1", "@rollup/rollup-linux-loong64-musl": "4.60.1", "@rollup/rollup-linux-ppc64-gnu": "4.60.1", "@rollup/rollup-linux-ppc64-musl": "4.60.1", "@rollup/rollup-linux-riscv64-gnu": "4.60.1", "@rollup/rollup-linux-riscv64-musl": "4.60.1", "@rollup/rollup-linux-s390x-gnu": "4.60.1", "@rollup/rollup-linux-x64-gnu": "4.60.1", "@rollup/rollup-linux-x64-musl": "4.60.1", "@rollup/rollup-openbsd-x64": "4.60.1", "@rollup/rollup-openharmony-arm64": "4.60.1", "@rollup/rollup-win32-arm64-msvc": "4.60.1", "@rollup/rollup-win32-ia32-msvc": "4.60.1", "@rollup/rollup-win32-x64-gnu": "4.60.1", "@rollup/rollup-win32-x64-msvc": "4.60.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w=="], + + "rollup-plugin-javascript-obfuscator": ["rollup-plugin-javascript-obfuscator@1.0.4", "", { "dependencies": { "javascript-obfuscator": "^0.18.1" } }, "sha512-pFn5NTqbjWDNMW2WIW9x+GecouGN5Y6fd6oOPLtLwbb0VlBoAiflrbW7WqK1k19ptEIAf5IfAYv0GNIVefhw/A=="], + + "run-async": ["run-async@2.4.1", "", {}, "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ=="], + + "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], + + "rx": ["rx@4.1.0", "", {}, "sha512-CiaiuN6gapkdl+cZUr67W6I8jquN4lkak3vtIsIWCl4XIPP8ffsoyN6/+PuGXnQy8Cu8W2y9Xxh31Rq4M6wUug=="], + + "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], + + "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], + + "sass": ["sass@1.98.0", "", { "dependencies": { "chokidar": "^4.0.0", "immutable": "^5.1.5", "source-map-js": ">=0.6.2 <2.0.0" }, "optionalDependencies": { "@parcel/watcher": "^2.4.1" }, "bin": { "sass": "sass.js" } }, "sha512-+4N/u9dZ4PrgzGgPlKnaaRQx64RO0JBKs9sDhQ2pLgN6JQZ25uPQZKQYaBJU48Kd5BxgXoJ4e09Dq7nMcOUW3A=="], + + "sax": ["sax@1.6.0", "", {}, "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA=="], + + "semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], + + "set-blocking": ["set-blocking@2.0.0", "", {}, "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw=="], + + "sharp": ["sharp@0.28.3", "", { "dependencies": { "color": "^3.1.3", "detect-libc": "^1.0.3", "node-addon-api": "^3.2.0", "prebuild-install": "^6.1.2", "semver": "^7.3.5", "simple-get": "^3.1.0", "tar-fs": "^2.1.1", "tunnel-agent": "^0.6.0" } }, "sha512-21GEP45Rmr7q2qcmdnjDkNP04Ooh5v0laGS5FDpojOO84D1DJwUijLiSq8XNNM6e8aGXYtoYRh3sVNdm8NodMA=="], + + "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], + + "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], + + "siginfo": ["siginfo@2.0.0", "", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="], + + "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], + + "simple-concat": ["simple-concat@1.0.1", "", {}, "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q=="], + + "simple-get": ["simple-get@3.1.1", "", { "dependencies": { "decompress-response": "^4.2.0", "once": "^1.3.1", "simple-concat": "^1.0.0" } }, "sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA=="], + + "simple-swizzle": ["simple-swizzle@0.2.4", "", { "dependencies": { "is-arrayish": "^0.3.1" } }, "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw=="], + + "slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="], + + "slice-ansi": ["slice-ansi@4.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "astral-regex": "^2.0.0", "is-fullwidth-code-point": "^3.0.0" } }, "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ=="], + + "source-map": ["source-map@0.2.0", "", { "dependencies": { "amdefine": ">=0.0.4" } }, "sha512-CBdZ2oa/BHhS4xj5DlhjWNHcan57/5YuvfdLf17iVmIpd9KRm+DFLmC6nBNj+6Ua7Kt3TmOjDpQT1aTYOQtoUA=="], + + "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + + "source-map-support": ["source-map-support@0.5.16", "", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ=="], + + "sshpk": ["sshpk@1.18.0", "", { "dependencies": { "asn1": "~0.2.3", "assert-plus": "^1.0.0", "bcrypt-pbkdf": "^1.0.0", "dashdash": "^1.12.0", "ecc-jsbn": "~0.1.1", "getpass": "^0.1.1", "jsbn": "~0.1.0", "safer-buffer": "^2.0.2", "tweetnacl": "~0.14.0" }, "bin": { "sshpk-conv": "bin/sshpk-conv", "sshpk-sign": "bin/sshpk-sign", "sshpk-verify": "bin/sshpk-verify" } }, "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ=="], + + "stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="], + + "std-env": ["std-env@4.0.0", "", {}, "sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ=="], + + "stream-to": ["stream-to@0.2.2", "", {}, "sha512-Kg1BSDTwgGiVMtTCJNlo7kk/xzL33ZuZveEBRt6rXw+f1WLK/8kmz2NVCT/Qnv0JkV85JOHcLhD82mnXsR3kPw=="], + + "stream-to-buffer": ["stream-to-buffer@0.1.0", "", { "dependencies": { "stream-to": "~0.2.0" } }, "sha512-Da4WoKaZyu3nf+bIdIifh7IPkFjARBnBK+pYqn0EUJqksjV9afojjaCCHUemH30Jmu7T2qcKvlZm2ykN38uzaw=="], + + "string-template": ["string-template@1.0.0", "", {}, "sha512-SLqR3GBUXuoPP5MmYtD7ompvXiG87QjT6lzOszyXjTM86Uu7At7vNnt2xgyTLq5o9T4IxTYFyGxcULqpsmsfdg=="], + + "string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="], + + "strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], + + "strtok3": ["strtok3@6.3.0", "", { "dependencies": { "@tokenizer/token": "^0.3.0", "peek-readable": "^4.1.0" } }, "sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw=="], + + "stylelint": ["stylelint@16.26.1", "", { "dependencies": { "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-syntax-patches-for-csstree": "^1.0.19", "@csstools/css-tokenizer": "^3.0.4", "@csstools/media-query-list-parser": "^4.0.3", "@csstools/selector-specificity": "^5.0.0", "@dual-bundle/import-meta-resolve": "^4.2.1", "balanced-match": "^2.0.0", "colord": "^2.9.3", "cosmiconfig": "^9.0.0", "css-functions-list": "^3.2.3", "css-tree": "^3.1.0", "debug": "^4.4.3", "fast-glob": "^3.3.3", "fastest-levenshtein": "^1.0.16", "file-entry-cache": "^11.1.1", "global-modules": "^2.0.0", "globby": "^11.1.0", "globjoin": "^0.1.4", "html-tags": "^3.3.1", "ignore": "^7.0.5", "imurmurhash": "^0.1.4", "is-plain-object": "^5.0.0", "known-css-properties": "^0.37.0", "mathml-tag-names": "^2.1.3", "meow": "^13.2.0", "micromatch": "^4.0.8", "normalize-path": "^3.0.0", "picocolors": "^1.1.1", "postcss": "^8.5.6", "postcss-resolve-nested-selector": "^0.1.6", "postcss-safe-parser": "^7.0.1", "postcss-selector-parser": "^7.1.0", "postcss-value-parser": "^4.2.0", "resolve-from": "^5.0.0", "string-width": "^4.2.3", "supports-hyperlinks": "^3.2.0", "svg-tags": "^1.0.0", "table": "^6.9.0", "write-file-atomic": "^5.0.1" }, "bin": { "stylelint": "bin/stylelint.mjs" } }, "sha512-v20V59/crfc8sVTAtge0mdafI3AdnzQ2KsWe6v523L4OA1bJO02S7MO2oyXDCS6iWb9ckIPnqAFVItqSBQr7jw=="], + + "stylelint-config-recommended": ["stylelint-config-recommended@14.0.1", "", { "peerDependencies": { "stylelint": "^16.1.0" } }, "sha512-bLvc1WOz/14aPImu/cufKAZYfXs/A/owZfSMZ4N+16WGXLoX5lOir53M6odBxvhgmgdxCVnNySJmZKx73T93cg=="], + + "stylelint-config-recommended-scss": ["stylelint-config-recommended-scss@14.1.0", "", { "dependencies": { "postcss-scss": "^4.0.9", "stylelint-config-recommended": "^14.0.1", "stylelint-scss": "^6.4.0" }, "peerDependencies": { "postcss": "^8.3.3", "stylelint": "^16.6.1" }, "optionalPeers": ["postcss"] }, "sha512-bhaMhh1u5dQqSsf6ri2GVWWQW5iUjBYgcHkh7SgDDn92ijoItC/cfO/W+fpXshgTQWhwFkP1rVcewcv4jaftRg=="], + + "stylelint-config-standard": ["stylelint-config-standard@36.0.1", "", { "dependencies": { "stylelint-config-recommended": "^14.0.1" }, "peerDependencies": { "stylelint": "^16.1.0" } }, "sha512-8aX8mTzJ6cuO8mmD5yon61CWuIM4UD8Q5aBcWKGSf6kg+EC3uhB+iOywpTK4ca6ZL7B49en8yanOFtUW0qNzyw=="], + + "stylelint-config-standard-scss": ["stylelint-config-standard-scss@13.1.0", "", { "dependencies": { "stylelint-config-recommended-scss": "^14.0.0", "stylelint-config-standard": "^36.0.0" }, "peerDependencies": { "postcss": "^8.3.3", "stylelint": "^16.3.1" }, "optionalPeers": ["postcss"] }, "sha512-Eo5w7/XvwGHWkeGLtdm2FZLOMYoZl1omP2/jgFCXyl2x5yNz7/8vv4Tj6slHvMSSUNTaGoam/GAZ0ZhukvalfA=="], + + "stylelint-scss": ["stylelint-scss@6.14.0", "", { "dependencies": { "css-tree": "^3.0.1", "is-plain-object": "^5.0.0", "known-css-properties": "^0.37.0", "mdn-data": "^2.25.0", "postcss-media-query-parser": "^0.2.3", "postcss-resolve-nested-selector": "^0.1.6", "postcss-selector-parser": "^7.1.1", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "stylelint": "^16.8.2" } }, "sha512-ZKmHMZolxeuYsnB+PCYrTpFce0/QWX9i9gh0hPXzp73WjuIMqUpzdQaBCrKoLWh6XtCFSaNDErkMPqdjy1/8aA=="], + + "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], + + "supports-hyperlinks": ["supports-hyperlinks@3.2.0", "", { "dependencies": { "has-flag": "^4.0.0", "supports-color": "^7.0.0" } }, "sha512-zFObLMyZeEwzAoKCyu1B91U79K2t7ApXuQfo8OuxwXLDgcKxuwM+YvcbIhm6QWqz7mHUH1TVytR1PwVVjEuMig=="], + + "svg-tags": ["svg-tags@1.0.0", "", {}, "sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA=="], + + "table": ["table@6.9.0", "", { "dependencies": { "ajv": "^8.0.1", "lodash.truncate": "^4.4.2", "slice-ansi": "^4.0.0", "string-width": "^4.2.3", "strip-ansi": "^6.0.1" } }, "sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A=="], + + "tailwindcss": ["tailwindcss@4.2.2", "", {}, "sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q=="], + + "tapable": ["tapable@2.3.2", "", {}, "sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA=="], + + "tar-fs": ["tar-fs@2.1.4", "", { "dependencies": { "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", "pump": "^3.0.0", "tar-stream": "^2.1.4" } }, "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ=="], + + "tar-stream": ["tar-stream@2.2.0", "", { "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", "fs-constants": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.1.1" } }, "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ=="], + + "through": ["through@2.3.8", "", {}, "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg=="], + + "through2": ["through2@4.0.2", "", { "dependencies": { "readable-stream": "3" } }, "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw=="], + + "timm": ["timm@1.7.1", "", {}, "sha512-IjZc9KIotudix8bMaBW6QvMuq64BrJWFs1+4V0lXwWGQZwH+LnX87doAYhem4caOEusRP9/g6jVDQmZ8XOk1nw=="], + + "tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="], + + "tinycolor2": ["tinycolor2@1.6.0", "", {}, "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw=="], + + "tinyexec": ["tinyexec@1.0.4", "", {}, "sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw=="], + + "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], + + "tinyrainbow": ["tinyrainbow@3.1.0", "", {}, "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw=="], + + "tmp": ["tmp@0.0.33", "", { "dependencies": { "os-tmpdir": "~1.0.2" } }, "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw=="], + + "to-ico": ["to-ico@1.1.5", "", { "dependencies": { "arrify": "^1.0.1", "buffer-alloc": "^1.1.0", "image-size": "^0.5.0", "parse-png": "^1.0.0", "resize-img": "^1.1.0" } }, "sha512-5kIh7m7bkIlqIESEZkL8gAMMzucXKfPe3hX2FoDY5HEAfD9OJU+Qh9b6Enp74w0qRcxVT5ejss66PHKqc3AVkg=="], + + "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], + + "token-types": ["token-types@4.2.1", "", { "dependencies": { "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" } }, "sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ=="], + + "tough-cookie": ["tough-cookie@2.5.0", "", { "dependencies": { "psl": "^1.1.28", "punycode": "^2.1.1" } }, "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g=="], + + "tslib": ["tslib@1.10.0", "", {}, "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ=="], + + "tunnel-agent": ["tunnel-agent@0.6.0", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w=="], + + "tweetnacl": ["tweetnacl@0.14.5", "", {}, "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA=="], + + "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], + + "undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="], + + "universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="], + + "update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="], + + "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], + + "url-regex": ["url-regex@3.2.0", "", { "dependencies": { "ip-regex": "^1.0.1" } }, "sha512-dQ9cJzMou5OKr6ZzfvwJkCq3rC72PNXhqz0v3EIhF4a3Np+ujr100AhUx2cKx5ei3iymoJpJrPB3sVSEMdqAeg=="], + + "utif": ["utif@2.0.1", "", { "dependencies": { "pako": "^1.0.5" } }, "sha512-Z/S1fNKCicQTf375lIP9G8Sa1H/phcysstNrrSdZKj1f9g58J4NMgb5IgiEZN9/nLMPDwF0W7hdOe9Qq2IYoLg=="], + + "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], + + "uuid": ["uuid@3.4.0", "", { "bin": { "uuid": "./bin/uuid" } }, "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A=="], + + "validator": ["validator@12.0.0", "", {}, "sha512-r5zA1cQBEOgYlesRmSEwc9LkbfNLTtji+vWyaHzRZUxCTHdsX3bd+sdHfs5tGZ2W6ILGGsxWxCNwT/h3IY/3ng=="], + + "verror": ["verror@1.10.0", "", { "dependencies": { "assert-plus": "^1.0.0", "core-util-is": "1.0.2", "extsprintf": "^1.2.0" } }, "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw=="], + + "vinyl": ["vinyl@2.2.1", "", { "dependencies": { "clone": "^2.1.1", "clone-buffer": "^1.0.0", "clone-stats": "^1.0.0", "cloneable-readable": "^1.0.0", "remove-trailing-separator": "^1.0.1", "replace-ext": "^1.0.0" } }, "sha512-LII3bXRFBZLlezoG5FfZVcXflZgWP/4dCwKtxd5ky9+LOtM4CS3bIRQsmR1KMnMW07jpE8fqR2lcxPZ+8sJIcw=="], + + "vite": ["vite@7.3.1", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA=="], + + "vite-plugin-compression": ["vite-plugin-compression@0.5.1", "", { "dependencies": { "chalk": "^4.1.2", "debug": "^4.3.3", "fs-extra": "^10.0.0" }, "peerDependencies": { "vite": ">=2.0.0" } }, "sha512-5QJKBDc+gNYVqL/skgFAP81Yuzo9R+EAf19d+EtsMF/i8kFUpNi3J/H01QD3Oo8zBQn+NzoCIFkpPLynoOzaJg=="], + + "vite-plugin-favicon": ["vite-plugin-favicon@1.0.8", "", { "dependencies": { "favicons": "^6.2.1", "find-root": "^1.1.0", "parse-author": "^2.0.0", "parse5": "^6.0.1" }, "peerDependencies": { "vite": ">=2.0.0" } }, "sha512-nJZkZ5/8J4FQvG+ZrVoG/lcrcQyk/dCAQz3E07I/2LmjcbckffSdCbtcso62NadRpPjLffVHKYgBya7DqrCdfA=="], + + "vitest": ["vitest@4.1.2", "", { "dependencies": { "@vitest/expect": "4.1.2", "@vitest/mocker": "4.1.2", "@vitest/pretty-format": "4.1.2", "@vitest/runner": "4.1.2", "@vitest/snapshot": "4.1.2", "@vitest/spy": "4.1.2", "@vitest/utils": "4.1.2", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", "obug": "^2.1.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", "std-env": "^4.0.0-rc.1", "tinybench": "^2.9.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tinyrainbow": "^3.1.0", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", "@vitest/browser-playwright": "4.1.2", "@vitest/browser-preview": "4.1.2", "@vitest/browser-webdriverio": "4.1.2", "@vitest/ui": "4.1.2", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@opentelemetry/api", "@types/node", "@vitest/browser-playwright", "@vitest/browser-preview", "@vitest/browser-webdriverio", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-xjR1dMTVHlFLh98JE3i/f/WePqJsah4A0FK9cc8Ehp9Udk0AZk6ccpIZhh1qJ/yxVWRZ+Q54ocnD8TXmkhspGg=="], + + "whatwg-mimetype": ["whatwg-mimetype@3.0.0", "", {}, "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q=="], + + "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], + + "why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="], + + "wide-align": ["wide-align@1.1.5", "", { "dependencies": { "string-width": "^1.0.2 || 2 || 3 || 4" } }, "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg=="], + + "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], + + "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], + + "write-file-atomic": ["write-file-atomic@5.0.1", "", { "dependencies": { "imurmurhash": "^0.1.4", "signal-exit": "^4.0.1" } }, "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw=="], + + "ws": ["ws@8.20.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA=="], + + "xhr": ["xhr@2.6.0", "", { "dependencies": { "global": "~4.4.0", "is-function": "^1.0.1", "parse-headers": "^2.0.0", "xtend": "^4.0.0" } }, "sha512-/eCGLb5rxjx5e3mF1A7s+pLlR6CGyqWN91fv1JgER5mVWg1MZmlhBvy9kjcsOdRk8RrIujotWyJamfyrp+WIcA=="], + + "xml-parse-from-string": ["xml-parse-from-string@1.0.1", "", {}, "sha512-ErcKwJTF54uRzzNMXq2X5sMIy88zJvfN2DmdoQvy7PAFJ+tPRU6ydWuOKNMyfmOjdyBQTFREi60s0Y0SyI0G0g=="], + + "xml2js": ["xml2js@0.4.23", "", { "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" } }, "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug=="], + + "xmlbuilder": ["xmlbuilder@11.0.1", "", {}, "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="], + + "xtend": ["xtend@4.0.2", "", {}, "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="], + + "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], + + "@babel/code-frame/js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + + "@cacheable/memory/keyv": ["keyv@5.6.0", "", { "dependencies": { "@keyv/serialize": "^1.1.1" } }, "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw=="], + + "@cacheable/utils/keyv": ["keyv@5.6.0", "", { "dependencies": { "@keyv/serialize": "^1.1.1" } }, "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw=="], + + "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], + + "@jimp/bmp/bmp-js": ["bmp-js@0.1.0", "", {}, "sha512-vHdS19CnY3hwiNdkaqk93DvjVLfbEcI8mys4UjuWrlX1haDmroo8o4xCzh4wD6DGV6HxRCyauwhHRqMTfERtjw=="], + + "@jimp/core/file-type": ["file-type@16.5.4", "", { "dependencies": { "readable-web-to-node-stream": "^3.0.0", "strtok3": "^6.2.4", "token-types": "^4.1.1" } }, "sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw=="], + + "@jimp/jpeg/jpeg-js": ["jpeg-js@0.4.4", "", {}, "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg=="], + + "@keyv/bigmap/keyv": ["keyv@5.6.0", "", { "dependencies": { "@keyv/serialize": "^1.1.1" } }, "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw=="], + + "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.9.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA=="], + + "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.9.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA=="], + + "@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg=="], + + "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.2", "", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" }, "bundled": true }, "sha512-sNXv5oLJ7ob93xkZ1XnxisYhGYXfaG9f65/ZgYuAu3qt7b3NadcOEhLvx28hv31PgX8SZJRYrAIPQilQmFpLVw=="], + + "@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], + + "@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "are-we-there-yet/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="], + + "babel-polyfill/regenerator-runtime": ["regenerator-runtime@0.10.5", "", {}, "sha512-02YopEIhAgiBHWeoTiA8aitHDt8z6w+rQqNuIftlM+ZtvSl/brTouaU7DW6GO/cHtvxJvS4Hwv2ibKdxIRi24w=="], + + "babel-runtime/regenerator-runtime": ["regenerator-runtime@0.11.1", "", {}, "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg=="], + + "brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + + "cacheable/keyv": ["keyv@5.6.0", "", { "dependencies": { "@keyv/serialize": "^1.1.1" } }, "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw=="], + + "cloneable-readable/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="], + + "color/color-convert": ["color-convert@1.9.3", "", { "dependencies": { "color-name": "1.1.3" } }, "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg=="], + + "encoding/iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], + + "escodegen-wallaby/estraverse": ["estraverse@1.9.3", "", {}, "sha512-25w1fMXQrGdoquWnScXZGckOv+Wes+JDnuN/+7ex3SauFRS72r2lFDec0EKPt2YD1wUJ/IrfEex+9yp4hfSOJA=="], + + "escodegen-wallaby/optionator": ["optionator@0.8.3", "", { "dependencies": { "deep-is": "~0.1.3", "fast-levenshtein": "~2.0.6", "levn": "~0.3.0", "prelude-ls": "~1.1.2", "type-check": "~0.3.2", "word-wrap": "~1.2.3" } }, "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA=="], + + "fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], + + "figures/escape-string-regexp": ["escape-string-regexp@1.0.5", "", {}, "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg=="], + + "gauge/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], + + "gauge/string-width": ["string-width@1.0.2", "", { "dependencies": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", "strip-ansi": "^3.0.0" } }, "sha512-0XsVpQLnVCXHJfyEs8tC0zpTVIr5PKKsQtkT29IwupnPTjtPmQ3xT/4yCREF9hYkV/3M3kzcUTSAZT6a6h81tw=="], + + "gauge/strip-ansi": ["strip-ansi@3.0.1", "", { "dependencies": { "ansi-regex": "^2.0.0" } }, "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg=="], + + "global-prefix/which": ["which@1.3.1", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "which": "./bin/which" } }, "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ=="], + + "has-ansi/ansi-regex": ["ansi-regex@2.1.1", "", {}, "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA=="], + + "image-q/@types/node": ["@types/node@16.9.1", "", {}, "sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g=="], + + "import-fresh/resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], + + "inquirer/chalk": ["chalk@1.1.3", "", { "dependencies": { "ansi-styles": "^2.2.1", "escape-string-regexp": "^1.0.2", "has-ansi": "^2.0.0", "strip-ansi": "^3.0.0", "supports-color": "^2.0.0" } }, "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A=="], + + "inquirer/string-width": ["string-width@2.1.1", "", { "dependencies": { "is-fullwidth-code-point": "^2.0.0", "strip-ansi": "^4.0.0" } }, "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw=="], + + "inquirer/strip-ansi": ["strip-ansi@3.0.1", "", { "dependencies": { "ansi-regex": "^2.0.0" } }, "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg=="], + + "javascript-obfuscator/chalk": ["chalk@3.0.0", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg=="], + + "javascript-obfuscator/espree": ["espree@6.2.1", "", { "dependencies": { "acorn": "^7.1.1", "acorn-jsx": "^5.2.0", "eslint-visitor-keys": "^1.1.0" } }, "sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw=="], + + "javascript-obfuscator/estraverse": ["estraverse@4.3.0", "", {}, "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw=="], + + "load-bmfont/phin": ["phin@3.7.1", "", { "dependencies": { "centra": "^2.7.0" } }, "sha512-GEazpTWwTZaEQ9RhL7Nyz0WwqilbqgLahDM3D0hxWwmVDI52nXEybHqiN6/elwpkJBhcuj+WbBu+QfT0uhPGfQ=="], + + "micromatch/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], + + "node-abi/semver": ["semver@5.7.2", "", { "bin": { "semver": "bin/semver" } }, "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g=="], + + "opencollective/chalk": ["chalk@1.1.3", "", { "dependencies": { "ansi-styles": "^2.2.1", "escape-string-regexp": "^1.0.2", "has-ansi": "^2.0.0", "strip-ansi": "^3.0.0", "supports-color": "^2.0.0" } }, "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A=="], + + "opencollective/minimist": ["minimist@1.2.0", "", {}, "sha512-7Wl+Jz+IGWuSdgsQEJ4JunV0si/iMhg42MnQQG6h1R6TNeVenp4U9x5CC5v/gYqz/fENLQITAWXidNtVL0NNbw=="], + + "parse-bmfont-xml/xml2js": ["xml2js@0.5.0", "", { "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" } }, "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA=="], + + "prebuild-install/detect-libc": ["detect-libc@1.0.3", "", { "bin": { "detect-libc": "./bin/detect-libc.js" } }, "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg=="], + + "prebuild-install/minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], + + "qified/hookified": ["hookified@2.1.1", "", {}, "sha512-AHb76R16GB5EsPBE2J7Ko5kiEyXwviB9P5SMrAKcuAu4vJPZttViAbj9+tZeaQE5zjDme+1vcHP78Yj/WoAveA=="], + + "rc/minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], + + "rc/strip-json-comments": ["strip-json-comments@2.0.1", "", {}, "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ=="], + + "readable-web-to-node-stream/readable-stream": ["readable-stream@4.7.0", "", { "dependencies": { "abort-controller": "^3.0.0", "buffer": "^6.0.3", "events": "^3.3.0", "process": "^0.11.10", "string_decoder": "^1.3.0" } }, "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg=="], + + "resize-img/jimp": ["jimp@0.2.28", "", { "dependencies": { "bignumber.js": "^2.1.0", "bmp-js": "0.0.3", "es6-promise": "^3.0.2", "exif-parser": "^0.1.9", "file-type": "^3.1.0", "jpeg-js": "^0.2.0", "load-bmfont": "^1.2.3", "mime": "^1.3.4", "mkdirp": "0.5.1", "pixelmatch": "^4.0.0", "pngjs": "^3.0.0", "read-chunk": "^1.0.1", "request": "^2.65.0", "stream-to-buffer": "^0.1.0", "tinycolor2": "^1.1.2", "url-regex": "^3.0.0" } }, "sha512-9HT7DA279xkTlry2oG30s6AtOUglNiY2UdyYpj0yNI4/NBv8PmdNC0gcldgMU4HqvbUlrM3+v+6GaHnTkH23JQ=="], + + "restore-cursor/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], + + "sharp/detect-libc": ["detect-libc@1.0.3", "", { "bin": { "detect-libc": "./bin/detect-libc.js" } }, "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg=="], + + "sharp/node-addon-api": ["node-addon-api@3.2.1", "", {}, "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A=="], + + "simple-swizzle/is-arrayish": ["is-arrayish@0.3.4", "", {}, "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA=="], + + "source-map-support/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], + + "stylelint/file-entry-cache": ["file-entry-cache@11.1.2", "", { "dependencies": { "flat-cache": "^6.1.20" } }, "sha512-N2WFfK12gmrK1c1GXOqiAJ1tc5YE+R53zvQ+t5P8S5XhnmKYVB5eZEiLNZKDSmoG8wqqbF9EXYBBW/nef19log=="], + + "stylelint/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], + + "table/ajv": ["ajv@8.18.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A=="], + + "to-ico/arrify": ["arrify@1.0.1", "", {}, "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA=="], + + "to-ico/image-size": ["image-size@0.5.5", "", { "bin": { "image-size": "bin/image-size.js" } }, "sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ=="], + + "verror/core-util-is": ["core-util-is@1.0.2", "", {}, "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ=="], + + "are-we-there-yet/readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="], + + "are-we-there-yet/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="], + + "cloneable-readable/readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="], + + "cloneable-readable/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="], + + "color/color-convert/color-name": ["color-name@1.1.3", "", {}, "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="], + + "escodegen-wallaby/optionator/levn": ["levn@0.3.0", "", { "dependencies": { "prelude-ls": "~1.1.2", "type-check": "~0.3.2" } }, "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA=="], + + "escodegen-wallaby/optionator/prelude-ls": ["prelude-ls@1.1.2", "", {}, "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w=="], + + "escodegen-wallaby/optionator/type-check": ["type-check@0.3.2", "", { "dependencies": { "prelude-ls": "~1.1.2" } }, "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg=="], + + "gauge/string-width/is-fullwidth-code-point": ["is-fullwidth-code-point@1.0.0", "", { "dependencies": { "number-is-nan": "^1.0.0" } }, "sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw=="], + + "gauge/strip-ansi/ansi-regex": ["ansi-regex@2.1.1", "", {}, "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA=="], + + "inquirer/chalk/ansi-styles": ["ansi-styles@2.2.1", "", {}, "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA=="], + + "inquirer/chalk/escape-string-regexp": ["escape-string-regexp@1.0.5", "", {}, "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg=="], + + "inquirer/chalk/supports-color": ["supports-color@2.0.0", "", {}, "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g=="], + + "inquirer/string-width/is-fullwidth-code-point": ["is-fullwidth-code-point@2.0.0", "", {}, "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w=="], + + "inquirer/string-width/strip-ansi": ["strip-ansi@4.0.0", "", { "dependencies": { "ansi-regex": "^3.0.0" } }, "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow=="], + + "inquirer/strip-ansi/ansi-regex": ["ansi-regex@2.1.1", "", {}, "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA=="], + + "javascript-obfuscator/espree/acorn": ["acorn@7.4.1", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A=="], + + "javascript-obfuscator/espree/eslint-visitor-keys": ["eslint-visitor-keys@1.3.0", "", {}, "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ=="], + + "opencollective/chalk/ansi-styles": ["ansi-styles@2.2.1", "", {}, "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA=="], + + "opencollective/chalk/escape-string-regexp": ["escape-string-regexp@1.0.5", "", {}, "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg=="], + + "opencollective/chalk/strip-ansi": ["strip-ansi@3.0.1", "", { "dependencies": { "ansi-regex": "^2.0.0" } }, "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg=="], + + "opencollective/chalk/supports-color": ["supports-color@2.0.0", "", {}, "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g=="], + + "readable-web-to-node-stream/readable-stream/buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="], + + "resize-img/jimp/bmp-js": ["bmp-js@0.0.3", "", {}, "sha512-epsm3Z92j5xwek9p97pVw3KbsNc0F4QnbYh+N93SpbJYuHFQQ/UAh6K+bKFGyLePH3Hudtl/Sa95Quqp0gX8IQ=="], + + "resize-img/jimp/jpeg-js": ["jpeg-js@0.2.0", "", {}, "sha512-Ni9PffhJtYtdD7VwxH6V2MnievekGfUefosGCHadog0/jAevRu6HPjYeMHbUemn0IPE8d4wGa8UsOGsX+iKy2g=="], + + "stylelint/file-entry-cache/flat-cache": ["flat-cache@6.1.22", "", { "dependencies": { "cacheable": "^2.3.4", "flatted": "^3.4.2", "hookified": "^1.15.0" } }, "sha512-N2dnzVJIphnNsjHcrxGW7DePckJ6haPrSFqpsBUhHYgwtKGVq4JrBGielEGD2fCVnsGm1zlBVZ8wGhkyuetgug=="], + + "table/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], + + "inquirer/string-width/strip-ansi/ansi-regex": ["ansi-regex@3.0.1", "", {}, "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw=="], + + "opencollective/chalk/strip-ansi/ansi-regex": ["ansi-regex@2.1.1", "", {}, "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA=="], + } +} diff --git a/compose.override.yaml b/compose.override.yaml deleted file mode 100644 index 8dc54de..0000000 --- a/compose.override.yaml +++ /dev/null @@ -1,18 +0,0 @@ - -services: -###> doctrine/doctrine-bundle ### - database: - ports: - - "5432" -###< doctrine/doctrine-bundle ### - -###> symfony/mailer ### - mailer: - image: axllent/mailpit - ports: - - "1025" - - "8025" - environment: - MP_SMTP_AUTH_ACCEPT_ANY: 1 - MP_SMTP_AUTH_ALLOW_INSECURE: 1 -###< symfony/mailer ### diff --git a/compose.yaml b/compose.yaml deleted file mode 100644 index 89c74d1..0000000 --- a/compose.yaml +++ /dev/null @@ -1,25 +0,0 @@ - -services: -###> doctrine/doctrine-bundle ### - database: - image: postgres:${POSTGRES_VERSION:-16}-alpine - environment: - POSTGRES_DB: ${POSTGRES_DB:-app} - # You should definitely change the password in production - POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-!ChangeMe!} - POSTGRES_USER: ${POSTGRES_USER:-app} - healthcheck: - test: ["CMD", "pg_isready", "-d", "${POSTGRES_DB:-app}", "-U", "${POSTGRES_USER:-app}"] - timeout: 5s - retries: 5 - start_period: 60s - volumes: - - database_data:/var/lib/postgresql/data:rw - # You may use a bind-mounted host directory instead, so that it is harder to accidentally remove the volume and lose all your data! - # - ./docker/db/data:/var/lib/postgresql/data:rw -###< doctrine/doctrine-bundle ### - -volumes: -###> doctrine/doctrine-bundle ### - database_data: -###< doctrine/doctrine-bundle ### diff --git a/composer.json b/composer.json index f80000e..a153335 100644 --- a/composer.json +++ b/composer.json @@ -7,48 +7,69 @@ "php": ">=8.4", "ext-ctype": "*", "ext-iconv": "*", - "doctrine/doctrine-bundle": "^3.2", + "doctrine/doctrine-bundle": "^3.2.2", "doctrine/doctrine-migrations-bundle": "^4.0", - "doctrine/orm": "^3.6", - "phpdocumentor/reflection-docblock": "^6.0", - "phpstan/phpdoc-parser": "^2.3", + "doctrine/orm": "^3.6.2", + "docusealco/docuseal-php": "^1.0", + "dompdf/dompdf": ">=3.1.5", + "endroid/qr-code-bundle": ">=6.1", + "knplabs/knp-paginator-bundle": "^6.10", + "knpuniversity/oauth2-client-bundle": "^2.20.2", + "league/flysystem-bundle": "^3.6.2", + "liip/imagine-bundle": "^2.17.1", + "meilisearch/meilisearch-php": "^1.16", + "mobiledetect/mobiledetectlib": ">=4.8.10", + "nelmio/security-bundle": "^3.9", + "phpdocumentor/reflection-docblock": "^6.0.3", + "phpstan/phpdoc-parser": "^2.3.2", + "scheb/2fa-backup-code": "^8.5", + "scheb/2fa-bundle": "^8.5", + "scheb/2fa-email": "^8.5", + "scheb/2fa-google-authenticator": "^8.5", + "spomky-labs/pwa-bundle": "1.5.7", + "stevenmaguire/oauth2-keycloak": "^6.1.1", + "stripe/stripe-php": ">=20", + "symfony/amazon-mailer": "8.0.*", "symfony/asset": "8.0.*", - "symfony/asset-mapper": "8.0.*", "symfony/console": "8.0.*", "symfony/doctrine-messenger": "8.0.*", "symfony/dotenv": "8.0.*", "symfony/expression-language": "8.0.*", - "symfony/flex": "^2", + "symfony/flex": "^2.10", "symfony/form": "8.0.*", "symfony/framework-bundle": "8.0.*", "symfony/http-client": "8.0.*", "symfony/intl": "8.0.*", "symfony/mailer": "8.0.*", "symfony/mime": "8.0.*", - "symfony/monolog-bundle": "^3.0|^4.0", + "symfony/monolog-bundle": "^3.0|^4.0.1", "symfony/notifier": "8.0.*", "symfony/process": "8.0.*", "symfony/property-access": "8.0.*", "symfony/property-info": "8.0.*", + "symfony/rate-limiter": "8.0.*", + "symfony/redis-messenger": "8.0.*", "symfony/runtime": "8.0.*", "symfony/security-bundle": "8.0.*", "symfony/serializer": "8.0.*", - "symfony/stimulus-bundle": "^2.34", "symfony/string": "8.0.*", "symfony/translation": "8.0.*", "symfony/twig-bundle": "8.0.*", - "symfony/ux-turbo": "^2.34", "symfony/validator": "8.0.*", "symfony/web-link": "8.0.*", "symfony/yaml": "8.0.*", - "twig/extra-bundle": "^2.12|^3.0", - "twig/twig": "^2.12|^3.0" + "twig/cssinliner-extra": "^3.24", + "twig/extra-bundle": "^2.12|^3.24", + "twig/inky-extra": "^3.24", + "twig/twig": "^2.12|^3.24", + "vich/uploader-bundle": "^2.9.2" }, "config": { "allow-plugins": { "php-http/discovery": true, "symfony/flex": true, - "symfony/runtime": true + "symfony/runtime": true, + "endroid/installer": true }, "bump-after-update": true, "sort-packages": true @@ -98,11 +119,13 @@ } }, "require-dev": { - "phpunit/phpunit": "^13.0", + "friendsofphp/php-cs-fixer": "^3.94.2", + "phpstan/phpstan": "^2.1.45", + "phpunit/phpunit": "^13.0.5", "symfony/browser-kit": "8.0.*", "symfony/css-selector": "8.0.*", "symfony/debug-bundle": "8.0.*", - "symfony/maker-bundle": "^1.0", + "symfony/maker-bundle": "^1.67", "symfony/stopwatch": "8.0.*", "symfony/web-profiler-bundle": "8.0.*" } diff --git a/composer.lock b/composer.lock index 9e367b2..64b30cd 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,268 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "67dc23159259e9bb9413afb21b3c167f", + "content-hash": "072dae6684c5bfe1ec9d9f2ffbd712ae", "packages": [ + { + "name": "async-aws/core", + "version": "1.28.1", + "source": { + "type": "git", + "url": "https://github.com/async-aws/core.git", + "reference": "e8b02ac30b17afaf1352cbd352dceb789d792d39" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/async-aws/core/zipball/e8b02ac30b17afaf1352cbd352dceb789d792d39", + "reference": "e8b02ac30b17afaf1352cbd352dceb789d792d39", + "shasum": "" + }, + "require": { + "ext-hash": "*", + "ext-simplexml": "*", + "php": "^8.2", + "psr/cache": "^1.0 || ^2.0 || ^3.0", + "psr/log": "^1.0 || ^2.0 || ^3.0", + "symfony/deprecation-contracts": "^2.1 || ^3.0", + "symfony/http-client": "^4.4.16 || ^5.1.7 || ^6.0 || ^7.0 || ^8.0", + "symfony/http-client-contracts": "^1.1.8 || ^2.0 || ^3.0", + "symfony/service-contracts": "^1.0 || ^2.0 || ^3.0" + }, + "conflict": { + "async-aws/s3": "<1.1", + "symfony/http-client": "5.2.0" + }, + "require-dev": { + "phpunit/phpunit": "^11.5.42", + "symfony/error-handler": "^7.3.2 || ^8.0", + "symfony/phpunit-bridge": "^7.3.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.28-dev" + } + }, + "autoload": { + "psr-4": { + "AsyncAws\\Core\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Core package to integrate with AWS. This is a lightweight AWS SDK provider by AsyncAws.", + "keywords": [ + "amazon", + "async-aws", + "aws", + "sdk", + "sts" + ], + "support": { + "source": "https://github.com/async-aws/core/tree/1.28.1" + }, + "funding": [ + { + "url": "https://github.com/jderusse", + "type": "github" + }, + { + "url": "https://github.com/nyholm", + "type": "github" + } + ], + "time": "2026-02-16T10:24:54+00:00" + }, + { + "name": "async-aws/ses", + "version": "1.14.1", + "source": { + "type": "git", + "url": "https://github.com/async-aws/ses.git", + "reference": "8ba4c7f5bbb4d1055f3ebedcf0ea1b8b79393e5b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/async-aws/ses/zipball/8ba4c7f5bbb4d1055f3ebedcf0ea1b8b79393e5b", + "reference": "8ba4c7f5bbb4d1055f3ebedcf0ea1b8b79393e5b", + "shasum": "" + }, + "require": { + "async-aws/core": "^1.9", + "php": "^8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.5.42", + "symfony/error-handler": "^7.3.2 || ^8.0", + "symfony/phpunit-bridge": "^7.3.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.14-dev" + } + }, + "autoload": { + "psr-4": { + "AsyncAws\\Ses\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "SES client, part of the AWS SDK provided by AsyncAws.", + "keywords": [ + "amazon", + "async-aws", + "aws", + "sdk", + "ses" + ], + "support": { + "source": "https://github.com/async-aws/ses/tree/1.14.1" + }, + "funding": [ + { + "url": "https://github.com/jderusse", + "type": "github" + }, + { + "url": "https://github.com/nyholm", + "type": "github" + } + ], + "time": "2026-02-16T10:24:54+00:00" + }, + { + "name": "bacon/bacon-qr-code", + "version": "v3.0.4", + "source": { + "type": "git", + "url": "https://github.com/Bacon/BaconQrCode.git", + "reference": "3feed0e212b8412cc5d2612706744789b0615824" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Bacon/BaconQrCode/zipball/3feed0e212b8412cc5d2612706744789b0615824", + "reference": "3feed0e212b8412cc5d2612706744789b0615824", + "shasum": "" + }, + "require": { + "dasprid/enum": "^1.0.3", + "ext-iconv": "*", + "php": "^8.1" + }, + "require-dev": { + "phly/keep-a-changelog": "^2.12", + "phpunit/phpunit": "^10.5.11 || ^11.0.4", + "spatie/phpunit-snapshot-assertions": "^5.1.5", + "spatie/pixelmatch-php": "^1.2.0", + "squizlabs/php_codesniffer": "^3.9" + }, + "suggest": { + "ext-imagick": "to generate QR code images" + }, + "type": "library", + "autoload": { + "psr-4": { + "BaconQrCode\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Ben Scholzen 'DASPRiD'", + "email": "mail@dasprids.de", + "homepage": "https://dasprids.de/", + "role": "Developer" + } + ], + "description": "BaconQrCode is a QR code generator for PHP.", + "homepage": "https://github.com/Bacon/BaconQrCode", + "support": { + "issues": "https://github.com/Bacon/BaconQrCode/issues", + "source": "https://github.com/Bacon/BaconQrCode/tree/v3.0.4" + }, + "time": "2026-03-16T01:01:30+00:00" + }, + { + "name": "composer/ca-bundle", + "version": "1.5.11", + "source": { + "type": "git", + "url": "https://github.com/composer/ca-bundle.git", + "reference": "68ff39175e8e94a4bb1d259407ce51a6a60f09e6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/68ff39175e8e94a4bb1d259407ce51a6a60f09e6", + "reference": "68ff39175e8e94a4bb1d259407ce51a6a60f09e6", + "shasum": "" + }, + "require": { + "ext-openssl": "*", + "ext-pcre": "*", + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^8 || ^9", + "psr/log": "^1.0 || ^2.0 || ^3.0", + "symfony/process": "^4.0 || ^5.0 || ^6.0 || ^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\CaBundle\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "Lets you find a path to the system CA bundle, and includes a fallback to the Mozilla CA bundle.", + "keywords": [ + "cabundle", + "cacert", + "certificate", + "ssl", + "tls" + ], + "support": { + "irc": "irc://irc.freenode.org/composer", + "issues": "https://github.com/composer/ca-bundle/issues", + "source": "https://github.com/composer/ca-bundle/tree/1.5.11" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + } + ], + "time": "2026-03-30T09:16:10+00:00" + }, { "name": "composer/semver", "version": "3.4.4", @@ -83,6 +343,56 @@ ], "time": "2025-08-20T19:15:30+00:00" }, + { + "name": "dasprid/enum", + "version": "1.0.7", + "source": { + "type": "git", + "url": "https://github.com/DASPRiD/Enum.git", + "reference": "b5874fa9ed0043116c72162ec7f4fb50e02e7cce" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/DASPRiD/Enum/zipball/b5874fa9ed0043116c72162ec7f4fb50e02e7cce", + "reference": "b5874fa9ed0043116c72162ec7f4fb50e02e7cce", + "shasum": "" + }, + "require": { + "php": ">=7.1 <9.0" + }, + "require-dev": { + "phpunit/phpunit": "^7 || ^8 || ^9 || ^10 || ^11", + "squizlabs/php_codesniffer": "*" + }, + "type": "library", + "autoload": { + "psr-4": { + "DASPRiD\\Enum\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Ben Scholzen 'DASPRiD'", + "email": "mail@dasprids.de", + "homepage": "https://dasprids.de/", + "role": "Developer" + } + ], + "description": "PHP 7.1 enum implementation", + "keywords": [ + "enum", + "map" + ], + "support": { + "issues": "https://github.com/DASPRiD/Enum/issues", + "source": "https://github.com/DASPRiD/Enum/tree/1.0.7" + }, + "time": "2025-09-16T12:23:56+00:00" + }, { "name": "doctrine/collections", "version": "2.6.0", @@ -1196,6 +1506,197 @@ }, "time": "2026-02-08T16:21:46+00:00" }, + { + "name": "docusealco/docuseal-php", + "version": "1.0.5", + "source": { + "type": "git", + "url": "https://github.com/docusealco/docuseal-php.git", + "reference": "f12a490e95bdb13ef61f46b72dffcdc646d8b0a4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/docusealco/docuseal-php/zipball/f12a490e95bdb13ef61f46b72dffcdc646d8b0a4", + "reference": "f12a490e95bdb13ef61f46b72dffcdc646d8b0a4", + "shasum": "" + }, + "type": "library", + "autoload": { + "psr-4": { + "Docuseal\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "DocuSeal" + } + ], + "description": "PHP bindings for DocuSeal API", + "support": { + "issues": "https://github.com/docusealco/docuseal-php/issues", + "source": "https://github.com/docusealco/docuseal-php/tree/1.0.5" + }, + "time": "2025-09-14T21:27:12+00:00" + }, + { + "name": "dompdf/dompdf", + "version": "v3.1.5", + "source": { + "type": "git", + "url": "https://github.com/dompdf/dompdf.git", + "reference": "f11ead23a8a76d0ff9bbc6c7c8fd7e05ca328496" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dompdf/dompdf/zipball/f11ead23a8a76d0ff9bbc6c7c8fd7e05ca328496", + "reference": "f11ead23a8a76d0ff9bbc6c7c8fd7e05ca328496", + "shasum": "" + }, + "require": { + "dompdf/php-font-lib": "^1.0.0", + "dompdf/php-svg-lib": "^1.0.0", + "ext-dom": "*", + "ext-mbstring": "*", + "masterminds/html5": "^2.0", + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "ext-gd": "*", + "ext-json": "*", + "ext-zip": "*", + "mockery/mockery": "^1.3", + "phpunit/phpunit": "^7.5 || ^8 || ^9 || ^10 || ^11", + "squizlabs/php_codesniffer": "^3.5", + "symfony/process": "^4.4 || ^5.4 || ^6.2 || ^7.0" + }, + "suggest": { + "ext-gd": "Needed to process images", + "ext-gmagick": "Improves image processing performance", + "ext-imagick": "Improves image processing performance", + "ext-zlib": "Needed for pdf stream compression" + }, + "type": "library", + "autoload": { + "psr-4": { + "Dompdf\\": "src/" + }, + "classmap": [ + "lib/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1" + ], + "authors": [ + { + "name": "The Dompdf Community", + "homepage": "https://github.com/dompdf/dompdf/blob/master/AUTHORS.md" + } + ], + "description": "DOMPDF is a CSS 2.1 compliant HTML to PDF converter", + "homepage": "https://github.com/dompdf/dompdf", + "support": { + "issues": "https://github.com/dompdf/dompdf/issues", + "source": "https://github.com/dompdf/dompdf/tree/v3.1.5" + }, + "time": "2026-03-03T13:54:37+00:00" + }, + { + "name": "dompdf/php-font-lib", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/dompdf/php-font-lib.git", + "reference": "a6e9a688a2a80016ac080b97be73d3e10c444c9a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dompdf/php-font-lib/zipball/a6e9a688a2a80016ac080b97be73d3e10c444c9a", + "reference": "a6e9a688a2a80016ac080b97be73d3e10c444c9a", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^7.5 || ^8 || ^9 || ^10 || ^11 || ^12" + }, + "type": "library", + "autoload": { + "psr-4": { + "FontLib\\": "src/FontLib" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1-or-later" + ], + "authors": [ + { + "name": "The FontLib Community", + "homepage": "https://github.com/dompdf/php-font-lib/blob/master/AUTHORS.md" + } + ], + "description": "A library to read, parse, export and make subsets of different types of font files.", + "homepage": "https://github.com/dompdf/php-font-lib", + "support": { + "issues": "https://github.com/dompdf/php-font-lib/issues", + "source": "https://github.com/dompdf/php-font-lib/tree/1.0.2" + }, + "time": "2026-01-20T14:10:26+00:00" + }, + { + "name": "dompdf/php-svg-lib", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/dompdf/php-svg-lib.git", + "reference": "8259ffb930817e72b1ff1caef5d226501f3dfeb1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dompdf/php-svg-lib/zipball/8259ffb930817e72b1ff1caef5d226501f3dfeb1", + "reference": "8259ffb930817e72b1ff1caef5d226501f3dfeb1", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": "^7.1 || ^8.0", + "sabberworm/php-css-parser": "^8.4 || ^9.0" + }, + "require-dev": { + "phpunit/phpunit": "^7.5 || ^8 || ^9 || ^10 || ^11" + }, + "type": "library", + "autoload": { + "psr-4": { + "Svg\\": "src/Svg" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "The SvgLib Community", + "homepage": "https://github.com/dompdf/php-svg-lib/blob/master/AUTHORS.md" + } + ], + "description": "A library to read, parse and export to PDF SVG files.", + "homepage": "https://github.com/dompdf/php-svg-lib", + "support": { + "issues": "https://github.com/dompdf/php-svg-lib/issues", + "source": "https://github.com/dompdf/php-svg-lib/tree/1.0.2" + }, + "time": "2026-01-02T16:01:13+00:00" + }, { "name": "egulias/email-validator", "version": "4.0.4", @@ -1263,6 +1764,1644 @@ ], "time": "2025-03-06T22:45:56+00:00" }, + { + "name": "endroid/installer", + "version": "1.5.2", + "source": { + "type": "git", + "url": "https://github.com/endroid/installer.git", + "reference": "32eacb1759b52c775cafa61a9da45e564d86de8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/endroid/installer/zipball/32eacb1759b52c775cafa61a9da45e564d86de8a", + "reference": "32eacb1759b52c775cafa61a9da45e564d86de8a", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^2.0", + "php": "^8.4" + }, + "require-dev": { + "composer/composer": "^2.0", + "endroid/quality": "dev-main" + }, + "suggest": { + "roave/security-advisories": "Avoids installation of package versions with vulnerabilities" + }, + "type": "composer-plugin", + "extra": { + "class": "Endroid\\Installer\\Installer", + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Endroid\\Installer\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jeroen van den Enden", + "email": "info@endroid.nl" + } + ], + "description": "Composer plugin for installing configuration files", + "support": { + "issues": "https://github.com/endroid/installer/issues", + "source": "https://github.com/endroid/installer/tree/1.5.2" + }, + "funding": [ + { + "url": "https://github.com/endroid", + "type": "github" + } + ], + "time": "2026-02-23T06:20:05+00:00" + }, + { + "name": "endroid/qr-code", + "version": "6.1.3", + "source": { + "type": "git", + "url": "https://github.com/endroid/qr-code.git", + "reference": "5fa534856ed95649d67c0eab0cabc03ab1d8e0e2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/endroid/qr-code/zipball/5fa534856ed95649d67c0eab0cabc03ab1d8e0e2", + "reference": "5fa534856ed95649d67c0eab0cabc03ab1d8e0e2", + "shasum": "" + }, + "require": { + "bacon/bacon-qr-code": "^3.0", + "php": "^8.4" + }, + "require-dev": { + "endroid/quality": "dev-main", + "ext-gd": "*", + "khanamiryan/qrcode-detector-decoder": "^2.0.3", + "setasign/fpdf": "^1.8.2" + }, + "suggest": { + "ext-gd": "Enables you to write PNG images", + "khanamiryan/qrcode-detector-decoder": "Enables you to use the image validator", + "roave/security-advisories": "Makes sure package versions with known security issues are not installed", + "setasign/fpdf": "Enables you to use the PDF writer" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.x-dev" + } + }, + "autoload": { + "psr-4": { + "Endroid\\QrCode\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jeroen van den Enden", + "email": "info@endroid.nl" + } + ], + "description": "Endroid QR Code", + "homepage": "https://github.com/endroid/qr-code", + "keywords": [ + "code", + "endroid", + "php", + "qr", + "qrcode" + ], + "support": { + "issues": "https://github.com/endroid/qr-code/issues", + "source": "https://github.com/endroid/qr-code/tree/6.1.3" + }, + "funding": [ + { + "url": "https://github.com/endroid", + "type": "github" + } + ], + "time": "2026-02-05T07:01:58+00:00" + }, + { + "name": "endroid/qr-code-bundle", + "version": "6.1.0", + "source": { + "type": "git", + "url": "https://github.com/endroid/qr-code-bundle.git", + "reference": "b6557949d735feee23d0cfb34969e005402ff7df" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/endroid/qr-code-bundle/zipball/b6557949d735feee23d0cfb34969e005402ff7df", + "reference": "b6557949d735feee23d0cfb34969e005402ff7df", + "shasum": "" + }, + "require": { + "endroid/installer": "^1.5", + "endroid/qr-code": "^6.1.0", + "php": "^8.4", + "symfony/framework-bundle": "^6.4||^7.4||^8.0", + "symfony/twig-bundle": "^6.4||^7.4||^8.0", + "symfony/yaml": "^6.4||^7.4||^8.0" + }, + "require-dev": { + "endroid/quality": "dev-main" + }, + "suggest": { + "roave/security-advisories": "Avoids installation of package versions with vulnerabilities" + }, + "type": "symfony-bundle", + "extra": { + "branch-alias": { + "dev-main": "6.x-dev" + } + }, + "autoload": { + "psr-4": { + "Endroid\\QrCodeBundle\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jeroen van den Enden", + "email": "info@endroid.nl" + } + ], + "description": "Endroid QR Code Bundle", + "homepage": "https://github.com/endroid/qr-code-bundle", + "keywords": [ + "bundle", + "code", + "endroid", + "php", + "qr", + "symfony" + ], + "support": { + "issues": "https://github.com/endroid/qr-code-bundle/issues", + "source": "https://github.com/endroid/qr-code-bundle/tree/6.1.0" + }, + "funding": [ + { + "url": "https://github.com/endroid", + "type": "github" + } + ], + "time": "2025-12-01T22:03:15+00:00" + }, + { + "name": "firebase/php-jwt", + "version": "v7.0.4", + "source": { + "type": "git", + "url": "https://github.com/firebase/php-jwt.git", + "reference": "e41f1bd7dbe3c5455c3f72d4338cfeb083b71931" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/firebase/php-jwt/zipball/e41f1bd7dbe3c5455c3f72d4338cfeb083b71931", + "reference": "e41f1bd7dbe3c5455c3f72d4338cfeb083b71931", + "shasum": "" + }, + "require": { + "php": "^8.0" + }, + "require-dev": { + "guzzlehttp/guzzle": "^7.4", + "phpfastcache/phpfastcache": "^9.2", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.5", + "psr/cache": "^2.0||^3.0", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.0" + }, + "suggest": { + "ext-sodium": "Support EdDSA (Ed25519) signatures", + "paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present" + }, + "type": "library", + "autoload": { + "psr-4": { + "Firebase\\JWT\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Neuman Vong", + "email": "neuman+pear@twilio.com", + "role": "Developer" + }, + { + "name": "Anant Narayanan", + "email": "anant@php.net", + "role": "Developer" + } + ], + "description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.", + "homepage": "https://github.com/firebase/php-jwt", + "keywords": [ + "jwt", + "php" + ], + "support": { + "issues": "https://github.com/firebase/php-jwt/issues", + "source": "https://github.com/firebase/php-jwt/tree/v7.0.4" + }, + "time": "2026-03-27T21:17:19+00:00" + }, + { + "name": "guzzlehttp/guzzle", + "version": "7.10.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "b51ac707cfa420b7bfd4e4d5e510ba8008e822b4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/b51ac707cfa420b7bfd4e4d5e510ba8008e822b4", + "reference": "b51ac707cfa420b7bfd4e4d5e510ba8008e822b4", + "shasum": "" + }, + "require": { + "ext-json": "*", + "guzzlehttp/promises": "^2.3", + "guzzlehttp/psr7": "^2.8", + "php": "^7.2.5 || ^8.0", + "psr/http-client": "^1.0", + "symfony/deprecation-contracts": "^2.2 || ^3.0" + }, + "provide": { + "psr/http-client-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "ext-curl": "*", + "guzzle/client-integration-tests": "3.0.2", + "php-http/message-factory": "^1.1", + "phpunit/phpunit": "^8.5.39 || ^9.6.20", + "psr/log": "^1.1 || ^2.0 || ^3.0" + }, + "suggest": { + "ext-curl": "Required for CURL handler support", + "ext-intl": "Required for Internationalized Domain Name (IDN) support", + "psr/log": "Required for using the Log middleware" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Jeremy Lindblom", + "email": "jeremeamia@gmail.com", + "homepage": "https://github.com/jeremeamia" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "psr-18", + "psr-7", + "rest", + "web service" + ], + "support": { + "issues": "https://github.com/guzzle/guzzle/issues", + "source": "https://github.com/guzzle/guzzle/tree/7.10.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle", + "type": "tidelift" + } + ], + "time": "2025-08-23T22:36:01+00:00" + }, + { + "name": "guzzlehttp/promises", + "version": "2.3.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "481557b130ef3790cf82b713667b43030dc9c957" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/481557b130ef3790cf82b713667b43030dc9c957", + "reference": "481557b130ef3790cf82b713667b43030dc9c957", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.44 || ^9.6.25" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], + "support": { + "issues": "https://github.com/guzzle/promises/issues", + "source": "https://github.com/guzzle/promises/tree/2.3.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises", + "type": "tidelift" + } + ], + "time": "2025-08-22T14:34:08+00:00" + }, + { + "name": "guzzlehttp/psr7", + "version": "2.9.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "7d0ed42f28e42d61352a7a79de682e5e67fec884" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/7d0ed42f28e42d61352a7a79de682e5e67fec884", + "reference": "7d0ed42f28e42d61352a7a79de682e5e67fec884", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.1 || ^2.0", + "ralouphie/getallheaders": "^3.0" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "http-interop/http-factory-tests": "0.9.0", + "jshttp/mime-db": "1.54.0.1", + "phpunit/phpunit": "^8.5.44 || ^9.6.25" + }, + "suggest": { + "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://sagikazarmark.hu" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "psr-7", + "request", + "response", + "stream", + "uri", + "url" + ], + "support": { + "issues": "https://github.com/guzzle/psr7/issues", + "source": "https://github.com/guzzle/psr7/tree/2.9.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7", + "type": "tidelift" + } + ], + "time": "2026-03-10T16:41:02+00:00" + }, + { + "name": "imagine/imagine", + "version": "1.5.2", + "source": { + "type": "git", + "url": "https://github.com/php-imagine/Imagine.git", + "reference": "f9ed796eefb77c2f0f2167e1d4e36bc2b5ed6b0c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-imagine/Imagine/zipball/f9ed796eefb77c2f0f2167e1d4e36bc2b5ed6b0c", + "reference": "f9ed796eefb77c2f0f2167e1d4e36bc2b5ed6b0c", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "phpunit/phpunit": "^4.8 || ^5.7 || ^6.5 || ^7.5 || ^8.4 || ^9.3" + }, + "suggest": { + "ext-exif": "to read EXIF metadata", + "ext-gd": "to use the GD implementation", + "ext-gmagick": "to use the Gmagick implementation", + "ext-imagick": "to use the Imagick implementation" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-develop": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Imagine\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bulat Shakirzyanov", + "email": "mallluhuct@gmail.com", + "homepage": "http://avalanche123.com" + } + ], + "description": "Image processing for PHP", + "homepage": "http://imagine.readthedocs.org/", + "keywords": [ + "drawing", + "graphics", + "image manipulation", + "image processing" + ], + "support": { + "issues": "https://github.com/php-imagine/Imagine/issues", + "source": "https://github.com/php-imagine/Imagine/tree/1.5.2" + }, + "time": "2026-01-09T10:45:12+00:00" + }, + { + "name": "jms/metadata", + "version": "2.9.0", + "source": { + "type": "git", + "url": "https://github.com/schmittjoh/metadata.git", + "reference": "554319d2e5f0c5d8ccaeffe755eac924e14da330" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/schmittjoh/metadata/zipball/554319d2e5f0c5d8ccaeffe755eac924e14da330", + "reference": "554319d2e5f0c5d8ccaeffe755eac924e14da330", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0" + }, + "require-dev": { + "doctrine/cache": "^1.0|^2.0", + "doctrine/coding-standard": "^8.0", + "mikey179/vfsstream": "^1.6.7", + "phpunit/phpunit": "^8.5.42|^9.6.23", + "psr/container": "^1.0|^2.0", + "symfony/cache": "^3.1|^4.0|^5.0|^6.0|^7.0|^8.0", + "symfony/dependency-injection": "^3.1|^4.0|^5.0|^6.0|^7.0|^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Metadata\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Johannes M. Schmitt", + "email": "schmittjoh@gmail.com" + }, + { + "name": "Asmir Mustafic", + "email": "goetas@gmail.com" + } + ], + "description": "Class/method/property metadata management in PHP", + "keywords": [ + "annotations", + "metadata", + "xml", + "yaml" + ], + "support": { + "issues": "https://github.com/schmittjoh/metadata/issues", + "source": "https://github.com/schmittjoh/metadata/tree/2.9.0" + }, + "time": "2025-11-30T20:12:26+00:00" + }, + { + "name": "knplabs/knp-components", + "version": "v5.2.0", + "source": { + "type": "git", + "url": "https://github.com/KnpLabs/knp-components.git", + "reference": "eabf39263fff305c0024820c3736e5b03e7edf50" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/KnpLabs/knp-components/zipball/eabf39263fff305c0024820c3736e5b03e7edf50", + "reference": "eabf39263fff305c0024820c3736e5b03e7edf50", + "shasum": "" + }, + "require": { + "php": "^8.1", + "symfony/event-dispatcher-contracts": "^3.0" + }, + "conflict": { + "doctrine/dbal": "<3.8" + }, + "require-dev": { + "doctrine/dbal": "^3.8 || ^4.0", + "doctrine/mongodb-odm": "^2.5.5", + "doctrine/orm": "^2.13 || ^3.0", + "doctrine/phpcr-odm": "^1.8 || ^2.0", + "ext-pdo_sqlite": "*", + "jackalope/jackalope-doctrine-dbal": "^1.12 || ^2.0", + "phpunit/phpunit": "^10.5 || ^11.3", + "propel/propel1": "^1.7", + "ruflin/elastica": "^7.0", + "solarium/solarium": "^6.0", + "symfony/http-foundation": "^5.4.38 || ^6.4.4 || ^7.0", + "symfony/http-kernel": "^5.4.38 || ^6.4.4 || ^7.0", + "symfony/property-access": "^5.4.38 || ^6.4.4 || ^7.0" + }, + "suggest": { + "doctrine/common": "to allow usage pagination with Doctrine ArrayCollection", + "doctrine/mongodb-odm": "to allow usage pagination with Doctrine ODM MongoDB", + "doctrine/orm": "to allow usage pagination with Doctrine ORM", + "doctrine/phpcr-odm": "to allow usage pagination with Doctrine ODM PHPCR", + "propel/propel1": "to allow usage pagination with Propel ORM", + "ruflin/elastica": "to allow usage pagination with ElasticSearch Client", + "solarium/solarium": "to allow usage pagination with Solarium Client", + "symfony/http-foundation": "to retrieve arguments from Request", + "symfony/property-access": "to allow sorting arrays" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "Knp\\Component\\": "src/Knp/Component" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "KnpLabs Team", + "homepage": "https://knplabs.com" + }, + { + "name": "Symfony Community", + "homepage": "https://github.com/KnpLabs/knp-components/contributors" + } + ], + "description": "Knplabs component library", + "homepage": "https://github.com/KnpLabs/knp-components", + "keywords": [ + "components", + "knp", + "knplabs", + "pager", + "paginator" + ], + "support": { + "issues": "https://github.com/KnpLabs/knp-components/issues", + "source": "https://github.com/KnpLabs/knp-components/tree/v5.2.0" + }, + "time": "2025-03-20T07:35:37+00:00" + }, + { + "name": "knplabs/knp-paginator-bundle", + "version": "v6.10.0", + "source": { + "type": "git", + "url": "https://github.com/KnpLabs/KnpPaginatorBundle.git", + "reference": "8d41f8ed47d880f8fa569389ffa4fecfbc5b8d41" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/KnpLabs/KnpPaginatorBundle/zipball/8d41f8ed47d880f8fa569389ffa4fecfbc5b8d41", + "reference": "8d41f8ed47d880f8fa569389ffa4fecfbc5b8d41", + "shasum": "" + }, + "require": { + "knplabs/knp-components": "^4.4 || ^5.0", + "php": "^8.1", + "symfony/config": "^6.4 || ^7.0 || ^8.0", + "symfony/dependency-injection": "^6.4 || ^7.0 || ^8.0", + "symfony/event-dispatcher": "^6.4 || ^7.0 || ^8.0", + "symfony/http-foundation": "^6.4 || ^7.0 || ^8.0", + "symfony/http-kernel": "^6.4 || ^7.0 || ^8.0", + "symfony/routing": "^6.4 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.12", + "phpunit/phpunit": "^10.5 || ^11.5 || ^12.2", + "symfony/templating": "^6.4 || ^7.0 || ^8.0", + "symfony/translation": "^6.4 || ^7.0 || ^8.0", + "twig/twig": "^3.0" + }, + "suggest": { + "symfony/translation": "To use the templates", + "twig/twig": "To use the templates" + }, + "type": "symfony-bundle", + "extra": { + "branch-alias": { + "dev-master": "6.x-dev" + } + }, + "autoload": { + "psr-4": { + "Knp\\Bundle\\PaginatorBundle\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "KnpLabs Team", + "homepage": "https://knplabs.com" + }, + { + "name": "Symfony Community", + "homepage": "https://github.com/KnpLabs/KnpPaginatorBundle/contributors" + } + ], + "description": "Paginator bundle for Symfony to automate pagination and simplify sorting and other features", + "homepage": "https://github.com/KnpLabs/KnpPaginatorBundle", + "keywords": [ + "bundle", + "knp", + "knplabs", + "pager", + "pagination", + "paginator", + "symfony" + ], + "support": { + "issues": "https://github.com/KnpLabs/KnpPaginatorBundle/issues", + "source": "https://github.com/KnpLabs/KnpPaginatorBundle/tree/v6.10.0" + }, + "time": "2025-11-29T09:14:09+00:00" + }, + { + "name": "knpuniversity/oauth2-client-bundle", + "version": "v2.20.2", + "source": { + "type": "git", + "url": "https://github.com/knpuniversity/oauth2-client-bundle.git", + "reference": "9ce4fcea69dbbf4d19ee7368b8d623ec2d73d3c7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/knpuniversity/oauth2-client-bundle/zipball/9ce4fcea69dbbf4d19ee7368b8d623ec2d73d3c7", + "reference": "9ce4fcea69dbbf4d19ee7368b8d623ec2d73d3c7", + "shasum": "" + }, + "require": { + "league/oauth2-client": "^2.0", + "php": ">=8.1", + "symfony/dependency-injection": "^6.4|^7.3|^8.0", + "symfony/framework-bundle": "^6.4|^7.3|^8.0", + "symfony/http-foundation": "^6.4|^7.3|^8.0", + "symfony/routing": "^6.4|^7.3|^8.0", + "symfony/security-core": "^6.4|^7.3|^8.0", + "symfony/security-http": "^6.4|^7.3|^8.0" + }, + "require-dev": { + "league/oauth2-facebook": "^1.1|^2.0", + "symfony/phpunit-bridge": "^7.3", + "symfony/yaml": "^6.4|^7.3|^8.0" + }, + "suggest": { + "symfony/security-guard": "For integration with Symfony's Guard Security layer" + }, + "type": "symfony-bundle", + "autoload": { + "psr-4": { + "KnpU\\OAuth2ClientBundle\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ryan Weaver", + "email": "ryan@symfonycasts.com" + } + ], + "description": "Integration with league/oauth2-client to provide services", + "homepage": "https://symfonycasts.com", + "keywords": [ + "oauth", + "oauth2" + ], + "support": { + "issues": "https://github.com/knpuniversity/oauth2-client-bundle/issues", + "source": "https://github.com/knpuniversity/oauth2-client-bundle/tree/v2.20.2" + }, + "time": "2026-02-12T17:07:18+00:00" + }, + { + "name": "league/flysystem", + "version": "3.33.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem.git", + "reference": "570b8871e0ce693764434b29154c54b434905350" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/570b8871e0ce693764434b29154c54b434905350", + "reference": "570b8871e0ce693764434b29154c54b434905350", + "shasum": "" + }, + "require": { + "league/flysystem-local": "^3.0.0", + "league/mime-type-detection": "^1.0.0", + "php": "^8.0.2" + }, + "conflict": { + "async-aws/core": "<1.19.0", + "async-aws/s3": "<1.14.0", + "aws/aws-sdk-php": "3.209.31 || 3.210.0", + "guzzlehttp/guzzle": "<7.0", + "guzzlehttp/ringphp": "<1.1.1", + "phpseclib/phpseclib": "3.0.15", + "symfony/http-client": "<5.2" + }, + "require-dev": { + "async-aws/s3": "^1.5 || ^2.0", + "async-aws/simple-s3": "^1.1 || ^2.0", + "aws/aws-sdk-php": "^3.295.10", + "composer/semver": "^3.0", + "ext-fileinfo": "*", + "ext-ftp": "*", + "ext-mongodb": "^1.3|^2", + "ext-zip": "*", + "friendsofphp/php-cs-fixer": "^3.5", + "google/cloud-storage": "^1.23", + "guzzlehttp/psr7": "^2.6", + "microsoft/azure-storage-blob": "^1.1", + "mongodb/mongodb": "^1.2|^2", + "phpseclib/phpseclib": "^3.0.36", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^9.5.11|^10.0", + "sabre/dav": "^4.6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\Flysystem\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" + } + ], + "description": "File storage abstraction for PHP", + "keywords": [ + "WebDAV", + "aws", + "cloud", + "file", + "files", + "filesystem", + "filesystems", + "ftp", + "s3", + "sftp", + "storage" + ], + "support": { + "issues": "https://github.com/thephpleague/flysystem/issues", + "source": "https://github.com/thephpleague/flysystem/tree/3.33.0" + }, + "time": "2026-03-25T07:59:30+00:00" + }, + { + "name": "league/flysystem-bundle", + "version": "3.6.2", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem-bundle.git", + "reference": "123ab96910177751faf3b6cc85eecc360ec12a1f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem-bundle/zipball/123ab96910177751faf3b6cc85eecc360ec12a1f", + "reference": "123ab96910177751faf3b6cc85eecc360ec12a1f", + "shasum": "" + }, + "require": { + "league/flysystem": "^3.0", + "php": ">=8.2", + "symfony/config": "^6.0 || ^7.0 || ^8.0", + "symfony/dependency-injection": "^6.0 || ^7.0 || ^8.0", + "symfony/deprecation-contracts": "^2.1 || ^3", + "symfony/http-kernel": "^6.0 || ^7.0 || ^8.0", + "symfony/options-resolver": "^6.0 || ^7.0 || ^8.0" + }, + "require-dev": { + "doctrine/mongodb-odm": "^2.0", + "league/flysystem-async-aws-s3": "^3.1", + "league/flysystem-aws-s3-v3": "^3.1", + "league/flysystem-azure-blob-storage": "^3.1", + "league/flysystem-ftp": "^3.1", + "league/flysystem-google-cloud-storage": "^3.1", + "league/flysystem-gridfs": "^3.28", + "league/flysystem-memory": "^3.1", + "league/flysystem-read-only": "^3.15", + "league/flysystem-sftp-v3": "^3.1", + "league/flysystem-webdav": "^3.29", + "platformcommunity/flysystem-bunnycdn": "^3.3", + "symfony/dotenv": "^6.0 || ^7.0 || ^8.0", + "symfony/framework-bundle": "^6.0 || ^7.0 || ^8.0", + "symfony/phpunit-bridge": "^6.0 || ^7.0 || ^8.0", + "symfony/var-dumper": "^6.0 || ^7.0 || ^8.0" + }, + "type": "symfony-bundle", + "autoload": { + "psr-4": { + "League\\FlysystemBundle\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Titouan Galopin", + "email": "galopintitouan@gmail.com" + } + ], + "description": "Symfony bundle integrating Flysystem into Symfony applications", + "keywords": [ + "Flysystem", + "bundle", + "filesystem", + "symfony" + ], + "support": { + "issues": "https://github.com/thephpleague/flysystem-bundle/issues", + "source": "https://github.com/thephpleague/flysystem-bundle/tree/3.6.2" + }, + "time": "2026-02-05T15:26:57+00:00" + }, + { + "name": "league/flysystem-local", + "version": "3.31.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem-local.git", + "reference": "2f669db18a4c20c755c2bb7d3a7b0b2340488079" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/2f669db18a4c20c755c2bb7d3a7b0b2340488079", + "reference": "2f669db18a4c20c755c2bb7d3a7b0b2340488079", + "shasum": "" + }, + "require": { + "ext-fileinfo": "*", + "league/flysystem": "^3.0.0", + "league/mime-type-detection": "^1.0.0", + "php": "^8.0.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\Flysystem\\Local\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" + } + ], + "description": "Local filesystem adapter for Flysystem.", + "keywords": [ + "Flysystem", + "file", + "files", + "filesystem", + "local" + ], + "support": { + "source": "https://github.com/thephpleague/flysystem-local/tree/3.31.0" + }, + "time": "2026-01-23T15:30:45+00:00" + }, + { + "name": "league/mime-type-detection", + "version": "1.16.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/mime-type-detection.git", + "reference": "2d6702ff215bf922936ccc1ad31007edc76451b9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/2d6702ff215bf922936ccc1ad31007edc76451b9", + "reference": "2d6702ff215bf922936ccc1ad31007edc76451b9", + "shasum": "" + }, + "require": { + "ext-fileinfo": "*", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.2", + "phpstan/phpstan": "^0.12.68", + "phpunit/phpunit": "^8.5.8 || ^9.3 || ^10.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\MimeTypeDetection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" + } + ], + "description": "Mime-type detection for Flysystem", + "support": { + "issues": "https://github.com/thephpleague/mime-type-detection/issues", + "source": "https://github.com/thephpleague/mime-type-detection/tree/1.16.0" + }, + "funding": [ + { + "url": "https://github.com/frankdejonge", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/league/flysystem", + "type": "tidelift" + } + ], + "time": "2024-09-21T08:32:55+00:00" + }, + { + "name": "league/oauth2-client", + "version": "2.9.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/oauth2-client.git", + "reference": "26e8c5da4f3d78cede7021e09b1330a0fc093d5e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/oauth2-client/zipball/26e8c5da4f3d78cede7021e09b1330a0fc093d5e", + "reference": "26e8c5da4f3d78cede7021e09b1330a0fc093d5e", + "shasum": "" + }, + "require": { + "ext-json": "*", + "guzzlehttp/guzzle": "^6.5.8 || ^7.4.5", + "php": "^7.1 || >=8.0.0 <8.6.0" + }, + "require-dev": { + "mockery/mockery": "^1.3.5", + "php-parallel-lint/php-parallel-lint": "^1.4", + "phpunit/phpunit": "^7 || ^8 || ^9 || ^10 || ^11", + "squizlabs/php_codesniffer": "^3.11" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\OAuth2\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Alex Bilbie", + "email": "hello@alexbilbie.com", + "homepage": "http://www.alexbilbie.com", + "role": "Developer" + }, + { + "name": "Woody Gilk", + "homepage": "https://github.com/shadowhand", + "role": "Contributor" + } + ], + "description": "OAuth 2.0 Client Library", + "keywords": [ + "Authentication", + "SSO", + "authorization", + "identity", + "idp", + "oauth", + "oauth2", + "single sign on" + ], + "support": { + "issues": "https://github.com/thephpleague/oauth2-client/issues", + "source": "https://github.com/thephpleague/oauth2-client/tree/2.9.0" + }, + "time": "2025-11-25T22:17:17+00:00" + }, + { + "name": "liip/imagine-bundle", + "version": "2.17.1", + "source": { + "type": "git", + "url": "https://github.com/liip/LiipImagineBundle.git", + "reference": "69d2df3c6606495d1878fa190d6c3dc4bc5623b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/liip/LiipImagineBundle/zipball/69d2df3c6606495d1878fa190d6c3dc4bc5623b6", + "reference": "69d2df3c6606495d1878fa190d6c3dc4bc5623b6", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "imagine/imagine": "^1.3.2", + "php": "^8.0", + "symfony/dependency-injection": "^5.4|^6.4|^7.4|^8.0", + "symfony/deprecation-contracts": "^2.5 || ^3", + "symfony/filesystem": "^5.4|^6.4|^7.3|^8.0", + "symfony/finder": "^5.4|^6.4|^7.3|^8.0", + "symfony/framework-bundle": "^5.4|^6.4|^7.3|^8.0", + "symfony/mime": "^5.4|^6.4|^7.3|^8.0", + "symfony/options-resolver": "^5.4|^6.4|^7.3|^8.0", + "symfony/process": "^5.4|^6.4|^7.3|^8.0", + "twig/twig": "^1.44|^2.9|^3.0" + }, + "require-dev": { + "amazonwebservices/aws-sdk-for-php": "^1.0", + "aws/aws-sdk-php": "^2.4|^3.0", + "doctrine/cache": "^1.11|^2.0", + "doctrine/persistence": "^1.3|^2.0", + "enqueue/enqueue-bundle": "^0.9|^0.10", + "ext-gd": "*", + "league/flysystem": "^1.0|^2.0|^3.0", + "phpstan/phpstan": "^1.10.0", + "psr/cache": "^1.0|^2.0|^3.0", + "psr/log": "^1.0", + "symfony/asset": "^5.4|^6.4|^7.3|^8.0", + "symfony/browser-kit": "^5.4|^6.4|^7.3|^8.0", + "symfony/cache": "^5.4|^6.4|^7.3|^8.0", + "symfony/console": "^5.4|^6.4|^7.3|^8.0", + "symfony/form": "^5.4|^6.4|^7.3|^8.0", + "symfony/messenger": "^5.4|^6.4|^7.3|^8.0", + "symfony/phpunit-bridge": "^7.3", + "symfony/runtime": "^5.4|^6.4|^7.3|^8.0", + "symfony/templating": "^5.4|^6.4|^7.3|^8.0", + "symfony/validator": "^5.4|^6.4|^7.3|^8.0", + "symfony/yaml": "^5.4|^6.4|^7.3|^8.0" + }, + "suggest": { + "alcaeus/mongo-php-adapter": "required for mongodb components", + "amazonwebservices/aws-sdk-for-php": "required to use AWS version 1 cache resolver", + "aws/aws-sdk-php": "required to use AWS version 2/3 cache resolver", + "doctrine/mongodb-odm": "required to use mongodb-backed doctrine components", + "enqueue/enqueue-bundle": "^0.9 add if you like to process images in background", + "ext-exif": "required to read EXIF metadata from images", + "ext-gd": "required to use gd driver", + "ext-gmagick": "required to use gmagick driver", + "ext-imagick": "required to use imagick driver", + "ext-json": "required to read JSON manifest versioning", + "ext-mongodb": "required for mongodb components", + "league/flysystem": "required to use FlySystem data loader or cache resolver", + "monolog/monolog": "A psr/log compatible logger is required to enable logging", + "rokka/imagine-vips": "required to use 'vips' driver", + "symfony/asset": "If you want to use asset versioning", + "symfony/messenger": "If you like to process images in background", + "symfony/templating": "required to use deprecated Templating component instead of Twig" + }, + "type": "symfony-bundle", + "autoload": { + "psr-4": { + "Liip\\ImagineBundle\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Liip and other contributors", + "homepage": "https://github.com/liip/LiipImagineBundle/contributors" + } + ], + "description": "This bundle provides an image manipulation abstraction toolkit for Symfony-based projects.", + "homepage": "https://www.liip.ch", + "keywords": [ + "bundle", + "image", + "imagine", + "liip", + "manipulation", + "photos", + "pictures", + "symfony", + "transformation" + ], + "support": { + "issues": "https://github.com/liip/LiipImagineBundle/issues", + "source": "https://github.com/liip/LiipImagineBundle/tree/2.17.1" + }, + "time": "2026-01-06T09:34:48+00:00" + }, + { + "name": "lorenzo/pinky", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/lorenzo/pinky.git", + "reference": "e1b1bdb2c132b8a7ba32bca64d2443f646ddbd17" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/lorenzo/pinky/zipball/e1b1bdb2c132b8a7ba32bca64d2443f646ddbd17", + "reference": "e1b1bdb2c132b8a7ba32bca64d2443f646ddbd17", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-xsl": "*", + "php": ">=5.6.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.21 || ^9.5.10" + }, + "type": "library", + "autoload": { + "files": [ + "src/pinky.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jose Lorenzo Rodriguez", + "email": "jose.zap@gmail.com" + } + ], + "description": "A Foundation for Emails (Inky) template transpiler", + "keywords": [ + "email", + "foundation", + "inky", + "template", + "zurb" + ], + "support": { + "issues": "https://github.com/lorenzo/pinky/issues", + "source": "https://github.com/lorenzo/pinky/tree/1.1.0" + }, + "time": "2023-07-31T13:36:50+00:00" + }, + { + "name": "masterminds/html5", + "version": "2.10.0", + "source": { + "type": "git", + "url": "https://github.com/Masterminds/html5-php.git", + "reference": "fcf91eb64359852f00d921887b219479b4f21251" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/fcf91eb64359852f00d921887b219479b4f21251", + "reference": "fcf91eb64359852f00d921887b219479b4f21251", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7.21 || ^6 || ^7 || ^8 || ^9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Masterminds\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matt Butcher", + "email": "technosophos@gmail.com" + }, + { + "name": "Matt Farina", + "email": "matt@mattfarina.com" + }, + { + "name": "Asmir Mustafic", + "email": "goetas@gmail.com" + } + ], + "description": "An HTML5 parser and serializer.", + "homepage": "http://masterminds.github.io/html5-php", + "keywords": [ + "HTML5", + "dom", + "html", + "parser", + "querypath", + "serializer", + "xml" + ], + "support": { + "issues": "https://github.com/Masterminds/html5-php/issues", + "source": "https://github.com/Masterminds/html5-php/tree/2.10.0" + }, + "time": "2025-07-25T09:04:22+00:00" + }, + { + "name": "meilisearch/meilisearch-php", + "version": "v1.16.1", + "source": { + "type": "git", + "url": "https://github.com/meilisearch/meilisearch-php.git", + "reference": "f9f63e0e7d12ffaae54f7317fa8f4f4dfa8ae7b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/meilisearch/meilisearch-php/zipball/f9f63e0e7d12ffaae54f7317fa8f4f4dfa8ae7b6", + "reference": "f9f63e0e7d12ffaae54f7317fa8f4f4dfa8ae7b6", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": "^7.4 || ^8.0", + "php-http/discovery": "^1.7", + "psr/http-client": "^1.0", + "symfony/polyfill-php81": "^1.33" + }, + "require-dev": { + "http-interop/http-factory-guzzle": "^1.2.0", + "php-cs-fixer/shim": "^3.59.3", + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-deprecation-rules": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^9.5 || ^10.5", + "symfony/http-client": "^5.4|^6.0|^7.0" + }, + "suggest": { + "guzzlehttp/guzzle": "Use Guzzle ^7 as HTTP client", + "http-interop/http-factory-guzzle": "Factory for guzzlehttp/guzzle", + "symfony/http-client": "Use Symfony Http client" + }, + "type": "library", + "autoload": { + "psr-4": { + "MeiliSearch\\": "src/", + "Meilisearch\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Clémentine Urquizar", + "email": "clementine@meilisearch.com" + }, + { + "name": "Bruno Casali", + "email": "bruno@meilisearch.com" + }, + { + "name": "Laurent Cazanove", + "email": "lau.cazanove@gmail.com" + }, + { + "name": "Tomas Norkūnas", + "email": "norkunas.tom@gmail.com" + } + ], + "description": "PHP wrapper for the Meilisearch API", + "keywords": [ + "api", + "client", + "instant", + "meilisearch", + "php", + "search" + ], + "support": { + "issues": "https://github.com/meilisearch/meilisearch-php/issues", + "source": "https://github.com/meilisearch/meilisearch-php/tree/v1.16.1" + }, + "time": "2025-09-18T10:15:45+00:00" + }, + { + "name": "mobiledetect/mobiledetectlib", + "version": "4.8.10", + "source": { + "type": "git", + "url": "https://github.com/serbanghita/Mobile-Detect.git", + "reference": "96b1e1fa9a968de7660a031106ab529f659d0192" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/serbanghita/Mobile-Detect/zipball/96b1e1fa9a968de7660a031106ab529f659d0192", + "reference": "96b1e1fa9a968de7660a031106ab529f659d0192", + "shasum": "" + }, + "require": { + "php": ">=8.0", + "psr/simple-cache": "^3" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^v3.75.0", + "phpbench/phpbench": "^1.2", + "phpstan/phpstan": "^2.1.11", + "phpunit/phpunit": "^9.6.22", + "squizlabs/php_codesniffer": "^3.12.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "Detection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Serban Ghita", + "email": "serbanghita@gmail.com", + "homepage": "http://mobiledetect.net", + "role": "Developer" + } + ], + "description": "Mobile_Detect is a lightweight PHP class for detecting mobile devices. It uses the User-Agent string combined with specific HTTP headers to detect the mobile environment.", + "homepage": "https://github.com/serbanghita/Mobile-Detect", + "keywords": [ + "detect mobile devices", + "mobile", + "mobile detect", + "mobile detector", + "php mobile detect" + ], + "support": { + "issues": "https://github.com/serbanghita/Mobile-Detect/issues", + "source": "https://github.com/serbanghita/Mobile-Detect/tree/4.8.10" + }, + "funding": [ + { + "url": "https://github.com/serbanghita", + "type": "github" + } + ], + "time": "2026-01-09T16:21:59+00:00" + }, { "name": "monolog/monolog", "version": "3.10.0", @@ -1367,240 +3506,226 @@ "time": "2026-01-02T08:56:05+00:00" }, { - "name": "myclabs/deep-copy", - "version": "1.13.4", + "name": "nelmio/security-bundle", + "version": "v3.9.0", "source": { "type": "git", - "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a" + "url": "https://github.com/nelmio/NelmioSecurityBundle.git", + "reference": "86dd4d12bc729498cd6f52b95ab6b36a66c72fd2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a", - "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "url": "https://api.github.com/repos/nelmio/NelmioSecurityBundle/zipball/86dd4d12bc729498cd6f52b95ab6b36a66c72fd2", + "reference": "86dd4d12bc729498cd6f52b95ab6b36a66c72fd2", "shasum": "" }, "require": { - "php": "^7.1 || ^8.0" - }, - "conflict": { - "doctrine/collections": "<1.6.8", - "doctrine/common": "<2.13.3 || >=3 <3.2.2" + "php": "^7.4 || ^8.0", + "symfony/deprecation-contracts": "^2.5 || ^3", + "symfony/framework-bundle": "^5.4 || ^6.3 || ^7.0 || ^8.0", + "symfony/http-kernel": "^5.4 || ^6.3 || ^7.0 || ^8.0", + "symfony/security-core": "^5.4 || ^6.3 || ^7.0 || ^8.0", + "symfony/security-csrf": "^5.4 || ^6.3 || ^7.0 || ^8.0", + "symfony/security-http": "^5.4 || ^6.3 || ^7.0 || ^8.0", + "symfony/yaml": "^5.4 || ^6.3 || ^7.0 || ^8.0", + "ua-parser/uap-php": "^3.4.4" }, "require-dev": { - "doctrine/collections": "^1.6.8", - "doctrine/common": "^2.13.3 || ^3.2.2", - "phpspec/prophecy": "^1.10", - "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-deprecation-rules": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpstan/phpstan-symfony": "^2.0", + "phpunit/phpunit": "^9.5 || ^10.1 || ^11.0", + "psr/cache": "^1.0 || ^2.0 || ^3.0", + "symfony/browser-kit": "^5.4 || ^6.3 || ^7.0 || ^8.0", + "symfony/cache": "^5.4 || ^6.3 || ^7.0 || ^8.0", + "symfony/phpunit-bridge": "^6.3 || ^7.0 || ^8.0", + "symfony/twig-bundle": "^5.4 || ^6.3 || ^7.0 || ^8.0", + "twig/twig": "^2.10 || ^3.0" + }, + "type": "symfony-bundle", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } }, - "type": "library", "autoload": { - "files": [ - "src/DeepCopy/deep_copy.php" - ], "psr-4": { - "DeepCopy\\": "src/DeepCopy/" + "Nelmio\\SecurityBundle\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "description": "Create deep copies (clones) of your objects", - "keywords": [ - "clone", - "copy", - "duplicate", - "object", - "object graph" - ], - "support": { - "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4" - }, - "funding": [ + "authors": [ { - "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", - "type": "tidelift" + "name": "Nelmio", + "homepage": "http://nelm.io" + }, + { + "name": "Symfony Community", + "homepage": "https://github.com/nelmio/NelmioSecurityBundle/contributors" } ], - "time": "2025-08-01T08:46:24+00:00" + "description": "Extra security-related features for Symfony: signed/encrypted cookies, HTTPS/SSL/HSTS handling, cookie session storage, ...", + "keywords": [ + "security" + ], + "support": { + "issues": "https://github.com/nelmio/NelmioSecurityBundle/issues", + "source": "https://github.com/nelmio/NelmioSecurityBundle/tree/v3.9.0" + }, + "time": "2026-02-23T10:58:33+00:00" }, { - "name": "nikic/php-parser", - "version": "v5.7.0", + "name": "paragonie/constant_time_encoding", + "version": "v3.1.3", "source": { "type": "git", - "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82" + "url": "https://github.com/paragonie/constant_time_encoding.git", + "reference": "d5b01a39b3415c2cd581d3bd3a3575c1ebbd8e77" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/dca41cd15c2ac9d055ad70dbfd011130757d1f82", - "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82", + "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/d5b01a39b3415c2cd581d3bd3a3575c1ebbd8e77", + "reference": "d5b01a39b3415c2cd581d3bd3a3575c1ebbd8e77", "shasum": "" }, "require": { - "ext-ctype": "*", - "ext-json": "*", - "ext-tokenizer": "*", - "php": ">=7.4" + "php": "^8" }, "require-dev": { - "ircmaxell/php-yacc": "^0.0.7", - "phpunit/phpunit": "^9.0" + "infection/infection": "^0", + "nikic/php-fuzzer": "^0", + "phpunit/phpunit": "^9|^10|^11", + "vimeo/psalm": "^4|^5|^6" }, - "bin": [ - "bin/php-parse" - ], "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.x-dev" + "autoload": { + "psr-4": { + "ParagonIE\\ConstantTime\\": "src/" } }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com", + "role": "Maintainer" + }, + { + "name": "Steve 'Sc00bz' Thomas", + "email": "steve@tobtu.com", + "homepage": "https://www.tobtu.com", + "role": "Original Developer" + } + ], + "description": "Constant-time Implementations of RFC 4648 Encoding (Base-64, Base-32, Base-16)", + "keywords": [ + "base16", + "base32", + "base32_decode", + "base32_encode", + "base64", + "base64_decode", + "base64_encode", + "bin2hex", + "encoding", + "hex", + "hex2bin", + "rfc4648" + ], + "support": { + "email": "info@paragonie.com", + "issues": "https://github.com/paragonie/constant_time_encoding/issues", + "source": "https://github.com/paragonie/constant_time_encoding" + }, + "time": "2025-09-24T15:06:41+00:00" + }, + { + "name": "php-http/discovery", + "version": "1.20.0", + "source": { + "type": "git", + "url": "https://github.com/php-http/discovery.git", + "reference": "82fe4c73ef3363caed49ff8dd1539ba06044910d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-http/discovery/zipball/82fe4c73ef3363caed49ff8dd1539ba06044910d", + "reference": "82fe4c73ef3363caed49ff8dd1539ba06044910d", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0|^2.0", + "php": "^7.1 || ^8.0" + }, + "conflict": { + "nyholm/psr7": "<1.0", + "zendframework/zend-diactoros": "*" + }, + "provide": { + "php-http/async-client-implementation": "*", + "php-http/client-implementation": "*", + "psr/http-client-implementation": "*", + "psr/http-factory-implementation": "*", + "psr/http-message-implementation": "*" + }, + "require-dev": { + "composer/composer": "^1.0.2|^2.0", + "graham-campbell/phpspec-skip-example-extension": "^5.0", + "php-http/httplug": "^1.0 || ^2.0", + "php-http/message-factory": "^1.0", + "phpspec/phpspec": "^5.1 || ^6.1 || ^7.3", + "sebastian/comparator": "^3.0.5 || ^4.0.8", + "symfony/phpunit-bridge": "^6.4.4 || ^7.0.1" + }, + "type": "composer-plugin", + "extra": { + "class": "Http\\Discovery\\Composer\\Plugin", + "plugin-optional": true + }, "autoload": { "psr-4": { - "PhpParser\\": "lib/PhpParser" - } + "Http\\Discovery\\": "src/" + }, + "exclude-from-classmap": [ + "src/Composer/Plugin.php" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Nikita Popov" + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" } ], - "description": "A PHP parser written in PHP", + "description": "Finds and installs PSR-7, PSR-17, PSR-18 and HTTPlug implementations", + "homepage": "http://php-http.org", "keywords": [ - "parser", - "php" + "adapter", + "client", + "discovery", + "factory", + "http", + "message", + "psr17", + "psr7" ], "support": { - "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.7.0" + "issues": "https://github.com/php-http/discovery/issues", + "source": "https://github.com/php-http/discovery/tree/1.20.0" }, - "time": "2025-12-06T11:56:16+00:00" - }, - { - "name": "phar-io/manifest", - "version": "2.0.4", - "source": { - "type": "git", - "url": "https://github.com/phar-io/manifest.git", - "reference": "54750ef60c58e43759730615a392c31c80e23176" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", - "reference": "54750ef60c58e43759730615a392c31c80e23176", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-libxml": "*", - "ext-phar": "*", - "ext-xmlwriter": "*", - "phar-io/version": "^3.0.1", - "php": "^7.2 || ^8.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" - }, - { - "name": "Sebastian Heuer", - "email": "sebastian@phpeople.de", - "role": "Developer" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "Developer" - } - ], - "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", - "support": { - "issues": "https://github.com/phar-io/manifest/issues", - "source": "https://github.com/phar-io/manifest/tree/2.0.4" - }, - "funding": [ - { - "url": "https://github.com/theseer", - "type": "github" - } - ], - "time": "2024-03-03T12:33:53+00:00" - }, - { - "name": "phar-io/version", - "version": "3.2.1", - "source": { - "type": "git", - "url": "https://github.com/phar-io/version.git", - "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", - "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" - }, - { - "name": "Sebastian Heuer", - "email": "sebastian@phpeople.de", - "role": "Developer" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "Developer" - } - ], - "description": "Library for handling version information and constraints", - "support": { - "issues": "https://github.com/phar-io/version/issues", - "source": "https://github.com/phar-io/version/tree/3.2.1" - }, - "time": "2022-02-21T01:04:05+00:00" + "time": "2024-10-02T11:20:13+00:00" }, { "name": "phpdocumentor/reflection-common", @@ -1825,494 +3950,6 @@ }, "time": "2026-01-25T14:56:51+00:00" }, - { - "name": "phpunit/php-code-coverage", - "version": "13.0.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "a8b58fde2f4fbc69a064e1f80ff917607cf7737c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/a8b58fde2f4fbc69a064e1f80ff917607cf7737c", - "reference": "a8b58fde2f4fbc69a064e1f80ff917607cf7737c", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-libxml": "*", - "ext-xmlwriter": "*", - "nikic/php-parser": "^5.7.0", - "php": ">=8.4", - "phpunit/php-file-iterator": "^7.0", - "phpunit/php-text-template": "^6.0", - "sebastian/complexity": "^6.0", - "sebastian/environment": "^9.0", - "sebastian/lines-of-code": "^5.0", - "sebastian/version": "^7.0", - "theseer/tokenizer": "^2.0.1" - }, - "require-dev": { - "phpunit/phpunit": "^13.0" - }, - "suggest": { - "ext-pcov": "PHP extension that provides line coverage", - "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "13.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", - "homepage": "https://github.com/sebastianbergmann/php-code-coverage", - "keywords": [ - "coverage", - "testing", - "xunit" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/13.0.1" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpunit/php-code-coverage", - "type": "tidelift" - } - ], - "time": "2026-02-06T06:05:15+00:00" - }, - { - "name": "phpunit/php-file-iterator", - "version": "7.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "6e5aa1fb0a95b1703d83e721299ee18bb4e2de50" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/6e5aa1fb0a95b1703d83e721299ee18bb4e2de50", - "reference": "6e5aa1fb0a95b1703d83e721299ee18bb4e2de50", - "shasum": "" - }, - "require": { - "php": ">=8.4" - }, - "require-dev": { - "phpunit/phpunit": "^13.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "7.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "FilterIterator implementation that filters files based on a list of suffixes.", - "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", - "keywords": [ - "filesystem", - "iterator" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", - "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", - "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/7.0.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpunit/php-file-iterator", - "type": "tidelift" - } - ], - "time": "2026-02-06T04:33:26+00:00" - }, - { - "name": "phpunit/php-invoker", - "version": "7.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-invoker.git", - "reference": "42e5c5cae0c65df12d1b1a3ab52bf3f50f244d88" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/42e5c5cae0c65df12d1b1a3ab52bf3f50f244d88", - "reference": "42e5c5cae0c65df12d1b1a3ab52bf3f50f244d88", - "shasum": "" - }, - "require": { - "php": ">=8.4" - }, - "require-dev": { - "ext-pcntl": "*", - "phpunit/phpunit": "^13.0" - }, - "suggest": { - "ext-pcntl": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "7.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Invoke callables with a timeout", - "homepage": "https://github.com/sebastianbergmann/php-invoker/", - "keywords": [ - "process" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-invoker/issues", - "security": "https://github.com/sebastianbergmann/php-invoker/security/policy", - "source": "https://github.com/sebastianbergmann/php-invoker/tree/7.0.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpunit/php-invoker", - "type": "tidelift" - } - ], - "time": "2026-02-06T04:34:47+00:00" - }, - { - "name": "phpunit/php-text-template", - "version": "6.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "a47af19f93f76aa3368303d752aa5272ca3299f4" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/a47af19f93f76aa3368303d752aa5272ca3299f4", - "reference": "a47af19f93f76aa3368303d752aa5272ca3299f4", - "shasum": "" - }, - "require": { - "php": ">=8.4" - }, - "require-dev": { - "phpunit/phpunit": "^13.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "6.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Simple template engine.", - "homepage": "https://github.com/sebastianbergmann/php-text-template/", - "keywords": [ - "template" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-text-template/issues", - "security": "https://github.com/sebastianbergmann/php-text-template/security/policy", - "source": "https://github.com/sebastianbergmann/php-text-template/tree/6.0.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpunit/php-text-template", - "type": "tidelift" - } - ], - "time": "2026-02-06T04:36:37+00:00" - }, - { - "name": "phpunit/php-timer", - "version": "9.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "a0e12065831f6ab0d83120dc61513eb8d9a966f6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/a0e12065831f6ab0d83120dc61513eb8d9a966f6", - "reference": "a0e12065831f6ab0d83120dc61513eb8d9a966f6", - "shasum": "" - }, - "require": { - "php": ">=8.4" - }, - "require-dev": { - "phpunit/phpunit": "^13.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "9.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Utility class for timing", - "homepage": "https://github.com/sebastianbergmann/php-timer/", - "keywords": [ - "timer" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-timer/issues", - "security": "https://github.com/sebastianbergmann/php-timer/security/policy", - "source": "https://github.com/sebastianbergmann/php-timer/tree/9.0.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpunit/php-timer", - "type": "tidelift" - } - ], - "time": "2026-02-06T04:37:53+00:00" - }, - { - "name": "phpunit/phpunit", - "version": "13.0.5", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "d57826e8921a534680c613924bfd921ded8047f4" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/d57826e8921a534680c613924bfd921ded8047f4", - "reference": "d57826e8921a534680c613924bfd921ded8047f4", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-json": "*", - "ext-libxml": "*", - "ext-mbstring": "*", - "ext-xml": "*", - "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.13.4", - "phar-io/manifest": "^2.0.4", - "phar-io/version": "^3.2.1", - "php": ">=8.4.1", - "phpunit/php-code-coverage": "^13.0.1", - "phpunit/php-file-iterator": "^7.0.0", - "phpunit/php-invoker": "^7.0.0", - "phpunit/php-text-template": "^6.0.0", - "phpunit/php-timer": "^9.0.0", - "sebastian/cli-parser": "^5.0.0", - "sebastian/comparator": "^8.0.0", - "sebastian/diff": "^8.0.0", - "sebastian/environment": "^9.0.0", - "sebastian/exporter": "^8.0.0", - "sebastian/global-state": "^9.0.0", - "sebastian/object-enumerator": "^8.0.0", - "sebastian/recursion-context": "^8.0.0", - "sebastian/type": "^7.0.0", - "sebastian/version": "^7.0.0", - "staabm/side-effects-detector": "^1.0.5" - }, - "bin": [ - "phpunit" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "13.0-dev" - } - }, - "autoload": { - "files": [ - "src/Framework/Assert/Functions.php" - ], - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "The PHP Unit Testing framework.", - "homepage": "https://phpunit.de/", - "keywords": [ - "phpunit", - "testing", - "xunit" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/13.0.5" - }, - "funding": [ - { - "url": "https://phpunit.de/sponsors.html", - "type": "custom" - }, - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", - "type": "tidelift" - } - ], - "time": "2026-02-18T12:40:03+00:00" - }, { "name": "psr/cache", "version": "3.0.0", @@ -2513,6 +4150,166 @@ }, "time": "2019-01-08T18:20:26+00:00" }, + { + "name": "psr/http-client", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-client.git", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP clients", + "homepage": "https://github.com/php-fig/http-client", + "keywords": [ + "http", + "http-client", + "psr", + "psr-18" + ], + "support": { + "source": "https://github.com/php-fig/http-client" + }, + "time": "2023-09-23T14:17:50+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory" + }, + "time": "2024-04-15T12:06:14+00:00" + }, + { + "name": "psr/http-message", + "version": "2.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/2.0" + }, + "time": "2023-04-04T09:54:51+00:00" + }, { "name": "psr/link", "version": "2.0.1", @@ -2620,1025 +4417,777 @@ "time": "2024-09-11T13:17:53+00:00" }, { - "name": "sebastian/cli-parser", - "version": "5.0.0", + "name": "psr/simple-cache", + "version": "3.0.0", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/cli-parser.git", - "reference": "48a4654fa5e48c1c81214e9930048a572d4b23ca" + "url": "https://github.com/php-fig/simple-cache.git", + "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/48a4654fa5e48c1c81214e9930048a572d4b23ca", - "reference": "48a4654fa5e48c1c81214e9930048a572d4b23ca", + "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/764e0b3939f5ca87cb904f570ef9be2d78a07865", + "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865", "shasum": "" }, "require": { - "php": ">=8.4" - }, - "require-dev": { - "phpunit/phpunit": "^13.0" + "php": ">=8.0.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "5.0-dev" + "dev-master": "3.0.x-dev" } }, "autoload": { - "classmap": [ - "src/" - ] + "psr-4": { + "Psr\\SimpleCache\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" } ], - "description": "Library for parsing CLI options", - "homepage": "https://github.com/sebastianbergmann/cli-parser", - "support": { - "issues": "https://github.com/sebastianbergmann/cli-parser/issues", - "security": "https://github.com/sebastianbergmann/cli-parser/security/policy", - "source": "https://github.com/sebastianbergmann/cli-parser/tree/5.0.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/sebastian/cli-parser", - "type": "tidelift" - } - ], - "time": "2026-02-06T04:39:44+00:00" - }, - { - "name": "sebastian/comparator", - "version": "8.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "29b232ddc29c2b114c0358c69b3084e7c3da0d58" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/29b232ddc29c2b114c0358c69b3084e7c3da0d58", - "reference": "29b232ddc29c2b114c0358c69b3084e7c3da0d58", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-mbstring": "*", - "php": ">=8.4", - "sebastian/diff": "^8.0", - "sebastian/exporter": "^8.0" - }, - "require-dev": { - "phpunit/phpunit": "^13.0" - }, - "suggest": { - "ext-bcmath": "For comparing BcMath\\Number objects" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "8.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Volker Dusch", - "email": "github@wallbash.com" - }, - { - "name": "Bernhard Schussek", - "email": "bschussek@2bepublished.at" - } - ], - "description": "Provides the functionality to compare PHP values for equality", - "homepage": "https://github.com/sebastianbergmann/comparator", + "description": "Common interfaces for simple caching", "keywords": [ - "comparator", - "compare", - "equality" + "cache", + "caching", + "psr", + "psr-16", + "simple-cache" ], "support": { - "issues": "https://github.com/sebastianbergmann/comparator/issues", - "security": "https://github.com/sebastianbergmann/comparator/security/policy", - "source": "https://github.com/sebastianbergmann/comparator/tree/8.0.0" + "source": "https://github.com/php-fig/simple-cache/tree/3.0.0" }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/sebastian/comparator", - "type": "tidelift" - } - ], - "time": "2026-02-06T04:40:39+00:00" + "time": "2021-10-29T13:26:27+00:00" }, { - "name": "sebastian/complexity", - "version": "6.0.0", + "name": "ralouphie/getallheaders", + "version": "3.0.3", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/complexity.git", - "reference": "c5651c795c98093480df79350cb050813fc7a2f3" + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "120b605dfeb996808c31b6477290a714d356e822" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/c5651c795c98093480df79350cb050813fc7a2f3", - "reference": "c5651c795c98093480df79350cb050813fc7a2f3", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", + "reference": "120b605dfeb996808c31b6477290a714d356e822", "shasum": "" }, "require": { - "nikic/php-parser": "^5.0", - "php": ">=8.4" + "php": ">=5.6" }, "require-dev": { - "phpunit/phpunit": "^13.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "6.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library for calculating the complexity of PHP code units", - "homepage": "https://github.com/sebastianbergmann/complexity", - "support": { - "issues": "https://github.com/sebastianbergmann/complexity/issues", - "security": "https://github.com/sebastianbergmann/complexity/security/policy", - "source": "https://github.com/sebastianbergmann/complexity/tree/6.0.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/sebastian/complexity", - "type": "tidelift" - } - ], - "time": "2026-02-06T04:41:32+00:00" - }, - { - "name": "sebastian/diff", - "version": "8.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "a2b6d09d7729ee87d605a439469f9dcc39be5ea3" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/a2b6d09d7729ee87d605a439469f9dcc39be5ea3", - "reference": "a2b6d09d7729ee87d605a439469f9dcc39be5ea3", - "shasum": "" - }, - "require": { - "php": ">=8.4" - }, - "require-dev": { - "phpunit/phpunit": "^13.0", - "symfony/process": "^7.2" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "8.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Kore Nordmann", - "email": "mail@kore-nordmann.de" - } - ], - "description": "Diff implementation", - "homepage": "https://github.com/sebastianbergmann/diff", - "keywords": [ - "diff", - "udiff", - "unidiff", - "unified diff" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/diff/issues", - "security": "https://github.com/sebastianbergmann/diff/security/policy", - "source": "https://github.com/sebastianbergmann/diff/tree/8.0.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/sebastian/diff", - "type": "tidelift" - } - ], - "time": "2026-02-06T04:42:27+00:00" - }, - { - "name": "sebastian/environment", - "version": "9.1.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "c4a2dc54b1a24e13ef1839cbb5947b967cbae853" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/c4a2dc54b1a24e13ef1839cbb5947b967cbae853", - "reference": "c4a2dc54b1a24e13ef1839cbb5947b967cbae853", - "shasum": "" - }, - "require": { - "php": ">=8.4" - }, - "require-dev": { - "phpunit/phpunit": "^13.0" - }, - "suggest": { - "ext-posix": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "9.1-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Provides functionality to handle HHVM/PHP environments", - "homepage": "https://github.com/sebastianbergmann/environment", - "keywords": [ - "Xdebug", - "environment", - "hhvm" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/environment/issues", - "security": "https://github.com/sebastianbergmann/environment/security/policy", - "source": "https://github.com/sebastianbergmann/environment/tree/9.1.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/sebastian/environment", - "type": "tidelift" - } - ], - "time": "2026-03-22T06:31:50+00:00" - }, - { - "name": "sebastian/exporter", - "version": "8.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "dc31f1f8e0186c8f0bb3e48fd4d51421d8905fea" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/dc31f1f8e0186c8f0bb3e48fd4d51421d8905fea", - "reference": "dc31f1f8e0186c8f0bb3e48fd4d51421d8905fea", - "shasum": "" - }, - "require": { - "ext-mbstring": "*", - "php": ">=8.4", - "sebastian/recursion-context": "^8.0" - }, - "require-dev": { - "phpunit/phpunit": "^13.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "8.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Volker Dusch", - "email": "github@wallbash.com" - }, - { - "name": "Adam Harvey", - "email": "aharvey@php.net" - }, - { - "name": "Bernhard Schussek", - "email": "bschussek@gmail.com" - } - ], - "description": "Provides the functionality to export PHP variables for visualization", - "homepage": "https://www.github.com/sebastianbergmann/exporter", - "keywords": [ - "export", - "exporter" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/exporter/issues", - "security": "https://github.com/sebastianbergmann/exporter/security/policy", - "source": "https://github.com/sebastianbergmann/exporter/tree/8.0.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/sebastian/exporter", - "type": "tidelift" - } - ], - "time": "2026-02-06T04:44:28+00:00" - }, - { - "name": "sebastian/global-state", - "version": "9.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "e52e3dc22441e6218c710afe72c3042f8fc41ea7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/e52e3dc22441e6218c710afe72c3042f8fc41ea7", - "reference": "e52e3dc22441e6218c710afe72c3042f8fc41ea7", - "shasum": "" - }, - "require": { - "php": ">=8.4", - "sebastian/object-reflector": "^6.0", - "sebastian/recursion-context": "^8.0" - }, - "require-dev": { - "ext-dom": "*", - "phpunit/phpunit": "^13.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "9.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Snapshotting of global state", - "homepage": "https://www.github.com/sebastianbergmann/global-state", - "keywords": [ - "global state" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/global-state/issues", - "security": "https://github.com/sebastianbergmann/global-state/security/policy", - "source": "https://github.com/sebastianbergmann/global-state/tree/9.0.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/sebastian/global-state", - "type": "tidelift" - } - ], - "time": "2026-02-06T04:45:13+00:00" - }, - { - "name": "sebastian/lines-of-code", - "version": "5.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/lines-of-code.git", - "reference": "4f21bb7768e1c997722ccc7efb1d6b5c11bfd471" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/4f21bb7768e1c997722ccc7efb1d6b5c11bfd471", - "reference": "4f21bb7768e1c997722ccc7efb1d6b5c11bfd471", - "shasum": "" - }, - "require": { - "nikic/php-parser": "^5.0", - "php": ">=8.4" - }, - "require-dev": { - "phpunit/phpunit": "^13.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "5.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library for counting the lines of code in PHP source code", - "homepage": "https://github.com/sebastianbergmann/lines-of-code", - "support": { - "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", - "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy", - "source": "https://github.com/sebastianbergmann/lines-of-code/tree/5.0.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/sebastian/lines-of-code", - "type": "tidelift" - } - ], - "time": "2026-02-06T04:45:54+00:00" - }, - { - "name": "sebastian/object-enumerator", - "version": "8.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "b39ab125fd9a7434b0ecbc4202eebce11a98cfc5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/b39ab125fd9a7434b0ecbc4202eebce11a98cfc5", - "reference": "b39ab125fd9a7434b0ecbc4202eebce11a98cfc5", - "shasum": "" - }, - "require": { - "php": ">=8.4", - "sebastian/object-reflector": "^6.0", - "sebastian/recursion-context": "^8.0" - }, - "require-dev": { - "phpunit/phpunit": "^13.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "8.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Traverses array structures and object graphs to enumerate all referenced objects", - "homepage": "https://github.com/sebastianbergmann/object-enumerator/", - "support": { - "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", - "security": "https://github.com/sebastianbergmann/object-enumerator/security/policy", - "source": "https://github.com/sebastianbergmann/object-enumerator/tree/8.0.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/sebastian/object-enumerator", - "type": "tidelift" - } - ], - "time": "2026-02-06T04:46:36+00:00" - }, - { - "name": "sebastian/object-reflector", - "version": "6.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/object-reflector.git", - "reference": "3ca042c2c60b0eab094f8a1b6a7093f4d4c72200" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/3ca042c2c60b0eab094f8a1b6a7093f4d4c72200", - "reference": "3ca042c2c60b0eab094f8a1b6a7093f4d4c72200", - "shasum": "" - }, - "require": { - "php": ">=8.4" - }, - "require-dev": { - "phpunit/phpunit": "^13.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "6.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Allows reflection of object attributes, including inherited and non-public ones", - "homepage": "https://github.com/sebastianbergmann/object-reflector/", - "support": { - "issues": "https://github.com/sebastianbergmann/object-reflector/issues", - "security": "https://github.com/sebastianbergmann/object-reflector/security/policy", - "source": "https://github.com/sebastianbergmann/object-reflector/tree/6.0.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/sebastian/object-reflector", - "type": "tidelift" - } - ], - "time": "2026-02-06T04:47:13+00:00" - }, - { - "name": "sebastian/recursion-context", - "version": "8.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "74c5af21f6a5833e91767ca068c4d3dfec15317e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/74c5af21f6a5833e91767ca068c4d3dfec15317e", - "reference": "74c5af21f6a5833e91767ca068c4d3dfec15317e", - "shasum": "" - }, - "require": { - "php": ">=8.4" - }, - "require-dev": { - "phpunit/phpunit": "^13.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "8.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Adam Harvey", - "email": "aharvey@php.net" - } - ], - "description": "Provides functionality to recursively process PHP variables", - "homepage": "https://github.com/sebastianbergmann/recursion-context", - "support": { - "issues": "https://github.com/sebastianbergmann/recursion-context/issues", - "security": "https://github.com/sebastianbergmann/recursion-context/security/policy", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/8.0.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/sebastian/recursion-context", - "type": "tidelift" - } - ], - "time": "2026-02-06T04:51:28+00:00" - }, - { - "name": "sebastian/type", - "version": "7.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/type.git", - "reference": "42412224607bd3931241bbd17f38e0f972f5a916" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/42412224607bd3931241bbd17f38e0f972f5a916", - "reference": "42412224607bd3931241bbd17f38e0f972f5a916", - "shasum": "" - }, - "require": { - "php": ">=8.4" - }, - "require-dev": { - "phpunit/phpunit": "^13.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "7.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Collection of value objects that represent the types of the PHP type system", - "homepage": "https://github.com/sebastianbergmann/type", - "support": { - "issues": "https://github.com/sebastianbergmann/type/issues", - "security": "https://github.com/sebastianbergmann/type/security/policy", - "source": "https://github.com/sebastianbergmann/type/tree/7.0.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/sebastian/type", - "type": "tidelift" - } - ], - "time": "2026-02-06T04:52:09+00:00" - }, - { - "name": "sebastian/version", - "version": "7.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/version.git", - "reference": "ad37a5552c8e2b88572249fdc19b6da7792e021b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/ad37a5552c8e2b88572249fdc19b6da7792e021b", - "reference": "ad37a5552c8e2b88572249fdc19b6da7792e021b", - "shasum": "" - }, - "require": { - "php": ">=8.4" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "7.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library that helps with managing the version number of Git-hosted PHP projects", - "homepage": "https://github.com/sebastianbergmann/version", - "support": { - "issues": "https://github.com/sebastianbergmann/version/issues", - "security": "https://github.com/sebastianbergmann/version/security/policy", - "source": "https://github.com/sebastianbergmann/version/tree/7.0.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/sebastian/version", - "type": "tidelift" - } - ], - "time": "2026-02-06T04:52:52+00:00" - }, - { - "name": "staabm/side-effects-detector", - "version": "1.0.5", - "source": { - "type": "git", - "url": "https://github.com/staabm/side-effects-detector.git", - "reference": "d8334211a140ce329c13726d4a715adbddd0a163" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/staabm/side-effects-detector/zipball/d8334211a140ce329c13726d4a715adbddd0a163", - "reference": "d8334211a140ce329c13726d4a715adbddd0a163", - "shasum": "" - }, - "require": { - "ext-tokenizer": "*", - "php": "^7.4 || ^8.0" - }, - "require-dev": { - "phpstan/extension-installer": "^1.4.3", - "phpstan/phpstan": "^1.12.6", - "phpunit/phpunit": "^9.6.21", - "symfony/var-dumper": "^5.4.43", - "tomasvotruba/type-coverage": "1.0.0", - "tomasvotruba/unused-public": "1.0.0" + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" }, "type": "library", "autoload": { - "classmap": [ - "lib/" + "files": [ + "src/getallheaders.php" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "description": "A static analysis tool to detect side effects in PHP code", + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } + ], + "description": "A polyfill for getallheaders.", + "support": { + "issues": "https://github.com/ralouphie/getallheaders/issues", + "source": "https://github.com/ralouphie/getallheaders/tree/develop" + }, + "time": "2019-03-08T08:55:37+00:00" + }, + { + "name": "sabberworm/php-css-parser", + "version": "v9.3.0", + "source": { + "type": "git", + "url": "https://github.com/MyIntervals/PHP-CSS-Parser.git", + "reference": "88dbd0f7f91abbfe4402d0a3071e9ff4d81ed949" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/MyIntervals/PHP-CSS-Parser/zipball/88dbd0f7f91abbfe4402d0a3071e9ff4d81ed949", + "reference": "88dbd0f7f91abbfe4402d0a3071e9ff4d81ed949", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "php": "^7.2.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0", + "thecodingmachine/safe": "^1.3 || ^2.5 || ^3.4" + }, + "require-dev": { + "php-parallel-lint/php-parallel-lint": "1.4.0", + "phpstan/extension-installer": "1.4.3", + "phpstan/phpstan": "1.12.32 || 2.1.32", + "phpstan/phpstan-phpunit": "1.4.2 || 2.0.8", + "phpstan/phpstan-strict-rules": "1.6.2 || 2.0.7", + "phpunit/phpunit": "8.5.52", + "rawr/phpunit-data-provider": "3.3.1", + "rector/rector": "1.2.10 || 2.2.8", + "rector/type-perfect": "1.0.0 || 2.1.0", + "squizlabs/php_codesniffer": "4.0.1", + "thecodingmachine/phpstan-safe-rule": "1.2.0 || 1.4.1" + }, + "suggest": { + "ext-mbstring": "for parsing UTF-8 CSS" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "9.4.x-dev" + } + }, + "autoload": { + "files": [ + "src/Rule/Rule.php", + "src/RuleSet/RuleContainer.php" + ], + "psr-4": { + "Sabberworm\\CSS\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Raphael Schweikert" + }, + { + "name": "Oliver Klee", + "email": "github@oliverklee.de" + }, + { + "name": "Jake Hotson", + "email": "jake.github@qzdesign.co.uk" + } + ], + "description": "Parser for CSS Files written in PHP", + "homepage": "https://www.sabberworm.com/blog/2010/6/10/php-css-parser", "keywords": [ - "static analysis" + "css", + "parser", + "stylesheet" ], "support": { - "issues": "https://github.com/staabm/side-effects-detector/issues", - "source": "https://github.com/staabm/side-effects-detector/tree/1.0.5" + "issues": "https://github.com/MyIntervals/PHP-CSS-Parser/issues", + "source": "https://github.com/MyIntervals/PHP-CSS-Parser/tree/v9.3.0" + }, + "time": "2026-03-03T17:31:43+00:00" + }, + { + "name": "scheb/2fa-backup-code", + "version": "v8.5.0", + "source": { + "type": "git", + "url": "https://github.com/scheb/2fa-backup-code.git", + "reference": "cf4251fcc24f4a39d1307d8bbfabecfbd21ed57b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/scheb/2fa-backup-code/zipball/cf4251fcc24f4a39d1307d8bbfabecfbd21ed57b", + "reference": "cf4251fcc24f4a39d1307d8bbfabecfbd21ed57b", + "shasum": "" + }, + "require": { + "php": "~8.4.0 || ~8.5.0", + "scheb/2fa-bundle": "self.version" + }, + "type": "library", + "autoload": { + "psr-4": { + "Scheb\\TwoFactorBundle\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Scheb", + "email": "me@christianscheb.de" + } + ], + "description": "Extends scheb/2fa-bundle with backup codes support", + "homepage": "https://github.com/scheb/2fa", + "keywords": [ + "2fa", + "Authentication", + "backup-codes", + "symfony", + "two-factor", + "two-step" + ], + "support": { + "source": "https://github.com/scheb/2fa-backup-code/tree/v8.5.0" + }, + "time": "2026-01-24T13:26:10+00:00" + }, + { + "name": "scheb/2fa-bundle", + "version": "v8.5.0", + "source": { + "type": "git", + "url": "https://github.com/scheb/2fa-bundle.git", + "reference": "ae26ae91723685e3a8622f2f3b9119016de23e20" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/scheb/2fa-bundle/zipball/ae26ae91723685e3a8622f2f3b9119016de23e20", + "reference": "ae26ae91723685e3a8622f2f3b9119016de23e20", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": "~8.4.0 || ~8.5.0", + "symfony/config": "^7.4 || ^8.0", + "symfony/dependency-injection": "^7.4 || ^8.0", + "symfony/event-dispatcher": "^7.4 || ^8.0", + "symfony/framework-bundle": "^7.4 || ^8.0", + "symfony/http-foundation": "^7.4 || ^8.0", + "symfony/http-kernel": "^7.4 || ^8.0", + "symfony/property-access": "^7.4 || ^8.0", + "symfony/security-bundle": "^7.4 || ^8.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/twig-bundle": "^7.4 || ^8.0" + }, + "conflict": { + "scheb/two-factor-bundle": "*" + }, + "suggest": { + "scheb/2fa-backup-code": "Emergency codes when you have no access to other methods", + "scheb/2fa-email": "Send codes by email", + "scheb/2fa-google-authenticator": "Google Authenticator support", + "scheb/2fa-totp": "Temporary one-time password (TOTP) support (Google Authenticator compatible)", + "scheb/2fa-trusted-device": "Trusted devices support" + }, + "type": "symfony-bundle", + "autoload": { + "psr-4": { + "Scheb\\TwoFactorBundle\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Scheb", + "email": "me@christianscheb.de" + } + ], + "description": "A generic interface to implement two-factor authentication in Symfony applications", + "homepage": "https://github.com/scheb/2fa", + "keywords": [ + "2fa", + "Authentication", + "symfony", + "two-factor", + "two-step" + ], + "support": { + "source": "https://github.com/scheb/2fa-bundle/tree/v8.5.0" + }, + "time": "2026-03-24T18:33:45+00:00" + }, + { + "name": "scheb/2fa-email", + "version": "v8.5.0", + "source": { + "type": "git", + "url": "https://github.com/scheb/2fa-email.git", + "reference": "6deee3e397c3c189738fd063011f1a512667eb13" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/scheb/2fa-email/zipball/6deee3e397c3c189738fd063011f1a512667eb13", + "reference": "6deee3e397c3c189738fd063011f1a512667eb13", + "shasum": "" + }, + "require": { + "php": "~8.4.0 || ~8.5.0", + "scheb/2fa-bundle": "self.version" + }, + "suggest": { + "symfony/mailer": "Needed if you want to use the default mailer implementation" + }, + "type": "library", + "autoload": { + "psr-4": { + "Scheb\\TwoFactorBundle\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Scheb", + "email": "me@christianscheb.de" + } + ], + "description": "Extends scheb/2fa-bundle with two-factor authentication via email", + "homepage": "https://github.com/scheb/2fa", + "keywords": [ + "2fa", + "Authentication", + "email", + "symfony", + "two-factor", + "two-step" + ], + "support": { + "source": "https://github.com/scheb/2fa-email/tree/v8.5.0" + }, + "time": "2026-01-24T13:26:10+00:00" + }, + { + "name": "scheb/2fa-google-authenticator", + "version": "v8.5.0", + "source": { + "type": "git", + "url": "https://github.com/scheb/2fa-google-authenticator.git", + "reference": "b7b173fa2cb437cb16d5a42af487e7e73a5f3ba1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/scheb/2fa-google-authenticator/zipball/b7b173fa2cb437cb16d5a42af487e7e73a5f3ba1", + "reference": "b7b173fa2cb437cb16d5a42af487e7e73a5f3ba1", + "shasum": "" + }, + "require": { + "php": "~8.4.0 || ~8.5.0", + "scheb/2fa-bundle": "self.version", + "spomky-labs/otphp": "^11.4" + }, + "suggest": { + "symfony/validator": "Needed if you want to use the Google Authenticator TOTP validator constraint" + }, + "type": "library", + "autoload": { + "psr-4": { + "Scheb\\TwoFactorBundle\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Scheb", + "email": "me@christianscheb.de" + } + ], + "description": "Extends scheb/2fa-bundle with two-factor authentication using Google Authenticator", + "homepage": "https://github.com/scheb/2fa", + "keywords": [ + "2fa", + "Authentication", + "google-authenticator", + "symfony", + "two-factor", + "two-step" + ], + "support": { + "source": "https://github.com/scheb/2fa-google-authenticator/tree/v8.5.0" + }, + "time": "2026-01-24T13:27:55+00:00" + }, + { + "name": "spomky-labs/otphp", + "version": "11.4.2", + "source": { + "type": "git", + "url": "https://github.com/Spomky-Labs/otphp.git", + "reference": "2a1b503fd1c1a5c751ab3c5cd37f2d2d26ab74ad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Spomky-Labs/otphp/zipball/2a1b503fd1c1a5c751ab3c5cd37f2d2d26ab74ad", + "reference": "2a1b503fd1c1a5c751ab3c5cd37f2d2d26ab74ad", + "shasum": "" + }, + "require": { + "paragonie/constant_time_encoding": "^2.0 || ^3.0", + "php": ">=8.1", + "psr/clock": "^1.0", + "symfony/deprecation-contracts": "^3.2" + }, + "require-dev": { + "symfony/error-handler": "^6.4|^7.0|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "OTPHP\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Florent Morselli", + "homepage": "https://github.com/Spomky" + }, + { + "name": "All contributors", + "homepage": "https://github.com/Spomky-Labs/otphp/contributors" + } + ], + "description": "A PHP library for generating one time passwords according to RFC 4226 (HOTP Algorithm) and the RFC 6238 (TOTP Algorithm) and compatible with Google Authenticator", + "homepage": "https://github.com/Spomky-Labs/otphp", + "keywords": [ + "FreeOTP", + "RFC 4226", + "RFC 6238", + "google authenticator", + "hotp", + "otp", + "totp" + ], + "support": { + "issues": "https://github.com/Spomky-Labs/otphp/issues", + "source": "https://github.com/Spomky-Labs/otphp/tree/11.4.2" }, "funding": [ { - "url": "https://github.com/staabm", + "url": "https://github.com/Spomky", "type": "github" + }, + { + "url": "https://www.patreon.com/FlorentMorselli", + "type": "patreon" } ], - "time": "2024-10-20T05:08:20+00:00" + "time": "2026-01-23T10:53:01+00:00" + }, + { + "name": "spomky-labs/pwa-bundle", + "version": "1.5.7", + "source": { + "type": "git", + "url": "https://github.com/Spomky-Labs/pwa-bundle.git", + "reference": "af032890aebac9311703edeebef4c3ddb4b36224" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Spomky-Labs/pwa-bundle/zipball/af032890aebac9311703edeebef4c3ddb4b36224", + "reference": "af032890aebac9311703edeebef4c3ddb4b36224", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "phpdocumentor/reflection-docblock": "^5.3|^6.0", + "psr/log": "^1.1|^2.0|^3.0", + "symfony/asset": "^6.4|^7.0|^8.0", + "symfony/asset-mapper": "^6.4|^7.0|^8.0", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/deprecation-contracts": "^3.5", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/property-access": "^6.4|^7.0|^8.0", + "symfony/property-info": "^6.4|^7.0|^8.0", + "symfony/routing": "^6.4|^7.0|^8.0", + "symfony/serializer": "^6.4|^7.0|^8.0", + "symfony/service-contracts": "^3.0", + "symfony/web-link": "^6.4|^7.0|^8.0", + "twig/twig": "^3.8" + }, + "require-dev": { + "dbrekelmans/bdi": "*", + "matthiasnoback/symfony-config-test": "^5.1|^6.0", + "monolog/monolog": "^3.0", + "nelmio/security-bundle": "^3.3", + "symfony/console": "^6.4|^7.0|^8.0", + "symfony/event-dispatcher": "^6.4|^7.0|^8.0", + "symfony/filesystem": "^6.4|^7.0|^8.0", + "symfony/finder": "^6.4|^7.0|^8.0", + "symfony/framework-bundle": "^7.0|^8.0", + "symfony/mime": "^6.4|^7.0|^8.0", + "symfony/monolog-bridge": "^6.4|^7.0|^8.0", + "symfony/monolog-bundle": "^3.10|^4.0", + "symfony/panther": "^2.3", + "symfony/translation": "^6.4|^7.0|^8.0", + "symfony/twig-bundle": "^7.0|^8.0", + "symfony/ux-icons": "^2.29", + "symfony/yaml": "^6.4|^7.0|^8.0" + }, + "suggest": { + "ext-gd": "Required to generate icons (or Imagick).", + "ext-imagick": "Required to generate icons (or GD).", + "symfony/filesystem": "For generating and manipulating icons or screenshots", + "symfony/mime": "For generating and manipulating icons or screenshots", + "symfony/ux-icons": "For using SVG icons (e.g. for shortcuts)" + }, + "type": "symfony-bundle", + "extra": { + "thanks": { + "url": "https://github.com/spomky-labs/pwa-bundle", + "name": "spomky-labs/pwa-bundle" + } + }, + "autoload": { + "psr-4": { + "SpomkyLabs\\PwaBundle\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Florent Morselli", + "homepage": "https://github.com/Spomky" + }, + { + "name": "All contributors", + "homepage": "https://github.com/spomky-labs/pwa-bundle/contributors" + } + ], + "description": "Progressive Web App Manifest Generator Bundle for Symfony.", + "homepage": "https://github.com/spomky-labs", + "keywords": [ + "bundle", + "pwa", + "symfony", + "symfony-ux" + ], + "support": { + "issues": "https://github.com/Spomky-Labs/pwa-bundle/issues", + "source": "https://github.com/Spomky-Labs/pwa-bundle/tree/1.5.7" + }, + "funding": [ + { + "url": "https://www.buymeacoffee.com/FlorentMorselli", + "type": "custom" + }, + { + "url": "https://github.com/Spomky", + "type": "github" + }, + { + "url": "https://www.patreon.com/FlorentMorselli", + "type": "patreon" + } + ], + "time": "2026-03-11T08:50:08+00:00" + }, + { + "name": "stevenmaguire/oauth2-keycloak", + "version": "6.1.1", + "source": { + "type": "git", + "url": "https://github.com/stevenmaguire/oauth2-keycloak.git", + "reference": "31bb3b1fa15b444212ed43facc898fafc7c2707a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/stevenmaguire/oauth2-keycloak/zipball/31bb3b1fa15b444212ed43facc898fafc7c2707a", + "reference": "31bb3b1fa15b444212ed43facc898fafc7c2707a", + "shasum": "" + }, + "require": { + "firebase/php-jwt": "^7.0", + "league/oauth2-client": "^2.8", + "php": "^8.0" + }, + "require-dev": { + "mockery/mockery": "^1.6", + "phpstan/phpstan": "^1.12", + "phpunit/phpunit": "~9.6.4", + "squizlabs/php_codesniffer": "~3.7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Stevenmaguire\\OAuth2\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Steven Maguire", + "email": "stevenmaguire@gmail.com", + "homepage": "https://github.com/stevenmaguire" + } + ], + "description": "Keycloak OAuth 2.0 Client Provider for The PHP League OAuth2-Client", + "keywords": [ + "authorisation", + "authorization", + "client", + "keycloak", + "oauth", + "oauth2" + ], + "support": { + "issues": "https://github.com/stevenmaguire/oauth2-keycloak/issues", + "source": "https://github.com/stevenmaguire/oauth2-keycloak/tree/6.1.1" + }, + "time": "2026-03-30T07:32:03+00:00" + }, + { + "name": "stripe/stripe-php", + "version": "v20.0.0", + "source": { + "type": "git", + "url": "https://github.com/stripe/stripe-php.git", + "reference": "7338bd140e641b1f9c7cb602e2de971e14db6b3b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/stripe/stripe-php/zipball/7338bd140e641b1f9c7cb602e2de971e14db6b3b", + "reference": "7338bd140e641b1f9c7cb602e2de971e14db6b3b", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "ext-json": "*", + "ext-mbstring": "*", + "php": ">=7.2.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "3.94.0", + "phpstan/phpstan": "^1.2", + "phpunit/phpunit": "^5.7 || ^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "files": [ + "lib/version_check.php" + ], + "psr-4": { + "Stripe\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Stripe and contributors", + "homepage": "https://github.com/stripe/stripe-php/contributors" + } + ], + "description": "Stripe PHP Library", + "homepage": "https://stripe.com/", + "keywords": [ + "api", + "payment processing", + "stripe" + ], + "support": { + "issues": "https://github.com/stripe/stripe-php/issues", + "source": "https://github.com/stripe/stripe-php/tree/v20.0.0" + }, + "time": "2026-03-26T01:57:48+00:00" + }, + { + "name": "symfony/amazon-mailer", + "version": "v8.0.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/amazon-mailer.git", + "reference": "db372af664e6ff8f315901a05fa7a844c7812954" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/amazon-mailer/zipball/db372af664e6ff8f315901a05fa7a844c7812954", + "reference": "db372af664e6ff8f315901a05fa7a844c7812954", + "shasum": "" + }, + "require": { + "async-aws/ses": "^1.8", + "php": ">=8.4", + "symfony/mailer": "^7.4|^8.0" + }, + "require-dev": { + "symfony/http-client": "^7.4|^8.0" + }, + "type": "symfony-mailer-bridge", + "autoload": { + "psr-4": { + "Symfony\\Component\\Mailer\\Bridge\\Amazon\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Amazon Mailer Bridge", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/amazon-mailer/tree/v8.0.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-02-12T08:52:49+00:00" }, { "name": "symfony/asset", @@ -3791,78 +5340,6 @@ ], "time": "2026-02-17T13:07:04+00:00" }, - { - "name": "symfony/browser-kit", - "version": "v8.0.4", - "source": { - "type": "git", - "url": "https://github.com/symfony/browser-kit.git", - "reference": "0d998c101e1920fc68572209d1316fec0db728ef" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/browser-kit/zipball/0d998c101e1920fc68572209d1316fec0db728ef", - "reference": "0d998c101e1920fc68572209d1316fec0db728ef", - "shasum": "" - }, - "require": { - "php": ">=8.4", - "symfony/dom-crawler": "^7.4|^8.0" - }, - "require-dev": { - "symfony/css-selector": "^7.4|^8.0", - "symfony/http-client": "^7.4|^8.0", - "symfony/mime": "^7.4|^8.0", - "symfony/process": "^7.4|^8.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\BrowserKit\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Simulates the behavior of a web browser, allowing you to make requests, click on links and submit forms programmatically", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/browser-kit/tree/v8.0.4" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2026-01-13T13:06:50+00:00" - }, { "name": "symfony/cache", "version": "v8.0.7", @@ -4353,81 +5830,6 @@ ], "time": "2026-02-17T13:07:04+00:00" }, - { - "name": "symfony/debug-bundle", - "version": "v8.0.7", - "source": { - "type": "git", - "url": "https://github.com/symfony/debug-bundle.git", - "reference": "a837fd8ee1c60805479d8ead3474e3d13a10af2b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/debug-bundle/zipball/a837fd8ee1c60805479d8ead3474e3d13a10af2b", - "reference": "a837fd8ee1c60805479d8ead3474e3d13a10af2b", - "shasum": "" - }, - "require": { - "composer-runtime-api": ">=2.1", - "ext-xml": "*", - "php": ">=8.4", - "symfony/config": "^7.4|^8.0", - "symfony/dependency-injection": "^7.4|^8.0", - "symfony/http-kernel": "^7.4|^8.0", - "symfony/twig-bridge": "^7.4|^8.0", - "symfony/var-dumper": "^7.4|^8.0" - }, - "require-dev": { - "symfony/web-profiler-bundle": "^7.4|^8.0" - }, - "type": "symfony-bundle", - "autoload": { - "psr-4": { - "Symfony\\Bundle\\DebugBundle\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides a tight integration of the Symfony VarDumper component and the ServerLogCommand from MonologBridge into the Symfony full-stack framework", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/debug-bundle/tree/v8.0.7" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2026-03-03T07:49:33+00:00" - }, { "name": "symfony/dependency-injection", "version": "v8.0.7", @@ -4754,76 +6156,6 @@ ], "time": "2026-02-20T07:51:53+00:00" }, - { - "name": "symfony/dom-crawler", - "version": "v8.0.6", - "source": { - "type": "git", - "url": "https://github.com/symfony/dom-crawler.git", - "reference": "7f504fe7fb7fa5fee40a653104842cf6f851a6d8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/7f504fe7fb7fa5fee40a653104842cf6f851a6d8", - "reference": "7f504fe7fb7fa5fee40a653104842cf6f851a6d8", - "shasum": "" - }, - "require": { - "php": ">=8.4", - "symfony/polyfill-ctype": "^1.8", - "symfony/polyfill-mbstring": "^1.0" - }, - "require-dev": { - "symfony/css-selector": "^7.4|^8.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\DomCrawler\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Eases DOM navigation for HTML and XML documents", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/dom-crawler/tree/v8.0.6" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2026-02-17T13:07:04+00:00" - }, { "name": "symfony/dotenv", "version": "v8.0.7", @@ -6180,105 +7512,6 @@ ], "time": "2026-02-25T16:59:43+00:00" }, - { - "name": "symfony/maker-bundle", - "version": "v1.67.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/maker-bundle.git", - "reference": "6ce8b313845f16bcf385ee3cb31d8b24e30d5516" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/maker-bundle/zipball/6ce8b313845f16bcf385ee3cb31d8b24e30d5516", - "reference": "6ce8b313845f16bcf385ee3cb31d8b24e30d5516", - "shasum": "" - }, - "require": { - "composer-runtime-api": "^2.1", - "doctrine/inflector": "^2.0", - "nikic/php-parser": "^5.0", - "php": ">=8.1", - "symfony/config": "^6.4|^7.0|^8.0", - "symfony/console": "^6.4|^7.0|^8.0", - "symfony/dependency-injection": "^6.4|^7.0|^8.0", - "symfony/deprecation-contracts": "^2.2|^3", - "symfony/filesystem": "^6.4|^7.0|^8.0", - "symfony/finder": "^6.4|^7.0|^8.0", - "symfony/framework-bundle": "^6.4|^7.0|^8.0", - "symfony/http-kernel": "^6.4|^7.0|^8.0", - "symfony/process": "^6.4|^7.0|^8.0" - }, - "conflict": { - "doctrine/doctrine-bundle": "<2.10", - "doctrine/orm": "<2.15" - }, - "require-dev": { - "composer/semver": "^3.0", - "doctrine/doctrine-bundle": "^2.10|^3.0", - "doctrine/orm": "^2.15|^3", - "doctrine/persistence": "^3.1|^4.0", - "symfony/http-client": "^6.4|^7.0|^8.0", - "symfony/phpunit-bridge": "^6.4.1|^7.0|^8.0", - "symfony/security-core": "^6.4|^7.0|^8.0", - "symfony/security-http": "^6.4|^7.0|^8.0", - "symfony/yaml": "^6.4|^7.0|^8.0", - "twig/twig": "^3.0|^4.x-dev" - }, - "type": "symfony-bundle", - "extra": { - "branch-alias": { - "dev-main": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Bundle\\MakerBundle\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony Maker helps you create empty commands, controllers, form classes, tests and more so you can forget about writing boilerplate code.", - "homepage": "https://symfony.com/doc/current/bundles/SymfonyMakerBundle/index.html", - "keywords": [ - "code generator", - "dev", - "generator", - "scaffold", - "scaffolding" - ], - "support": { - "issues": "https://github.com/symfony/maker-bundle/issues", - "source": "https://github.com/symfony/maker-bundle/tree/v1.67.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2026-03-18T13:39:06+00:00" - }, { "name": "symfony/messenger", "version": "v8.0.7", @@ -7576,6 +8809,156 @@ ], "time": "2026-03-04T15:54:04+00:00" }, + { + "name": "symfony/rate-limiter", + "version": "v8.0.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/rate-limiter.git", + "reference": "1f8159c50b55e78810f5a8f60889d0b6b3a11deb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/rate-limiter/zipball/1f8159c50b55e78810f5a8f60889d0b6b3a11deb", + "reference": "1f8159c50b55e78810f5a8f60889d0b6b3a11deb", + "shasum": "" + }, + "require": { + "php": ">=8.4", + "symfony/options-resolver": "^7.4|^8.0" + }, + "require-dev": { + "psr/cache": "^1.0|^2.0|^3.0", + "symfony/lock": "^7.4|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\RateLimiter\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Wouter de Jong", + "email": "wouter@wouterj.nl" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides a Token Bucket implementation to rate limit input and output in your application", + "homepage": "https://symfony.com", + "keywords": [ + "limiter", + "rate-limiter" + ], + "support": { + "source": "https://github.com/symfony/rate-limiter/tree/v8.0.7" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-03-04T13:55:34+00:00" + }, + { + "name": "symfony/redis-messenger", + "version": "v8.0.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/redis-messenger.git", + "reference": "706bbc32a8e061af1671c751f3e987bc5df4009b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/redis-messenger/zipball/706bbc32a8e061af1671c751f3e987bc5df4009b", + "reference": "706bbc32a8e061af1671c751f3e987bc5df4009b", + "shasum": "" + }, + "require": { + "ext-redis": "*", + "php": ">=8.4", + "symfony/messenger": "^7.4|^8.0" + }, + "conflict": { + "ext-redis": "<6.1", + "ext-relay": "<0.12" + }, + "require-dev": { + "symfony/property-access": "^7.4|^8.0", + "symfony/serializer": "^7.4|^8.0", + "symfony/var-dumper": "^7.4|^8.0" + }, + "type": "symfony-messenger-bridge", + "autoload": { + "psr-4": { + "Symfony\\Component\\Messenger\\Bridge\\Redis\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Redis extension Messenger Bridge", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/redis-messenger/tree/v8.0.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-02-14T18:07:45+00:00" + }, { "name": "symfony/routing", "version": "v8.0.6", @@ -8263,79 +9646,6 @@ ], "time": "2025-07-15T11:30:57+00:00" }, - { - "name": "symfony/stimulus-bundle", - "version": "v2.34.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/stimulus-bundle.git", - "reference": "d610a2e021cf63f955838b4bfe40da7e4cafe850" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/stimulus-bundle/zipball/d610a2e021cf63f955838b4bfe40da7e4cafe850", - "reference": "d610a2e021cf63f955838b4bfe40da7e4cafe850", - "shasum": "" - }, - "require": { - "php": ">=8.1", - "symfony/config": "^5.4|^6.0|^7.0|^8.0", - "symfony/dependency-injection": "^5.4|^6.0|^7.0|^8.0", - "symfony/deprecation-contracts": "^2.0|^3.0", - "symfony/finder": "^5.4|^6.0|^7.0|^8.0", - "symfony/http-kernel": "^5.4|^6.0|^7.0|^8.0", - "twig/twig": "^2.15.3|^3.8" - }, - "require-dev": { - "symfony/asset-mapper": "^6.3|^7.0|^8.0", - "symfony/framework-bundle": "^5.4|^6.0|^7.0|^8.0", - "symfony/phpunit-bridge": "^5.4|^6.0|^7.0|^8.0", - "symfony/twig-bundle": "^5.4|^6.0|^7.0|^8.0", - "zenstruck/browser": "^1.4" - }, - "type": "symfony-bundle", - "autoload": { - "psr-4": { - "Symfony\\UX\\StimulusBundle\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Integration with your Symfony app & Stimulus!", - "keywords": [ - "symfony-ux" - ], - "support": { - "source": "https://github.com/symfony/stimulus-bundle/tree/v2.34.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2026-03-21T22:29:11+00:00" - }, { "name": "symfony/stopwatch", "version": "v8.0.0", @@ -8940,109 +10250,6 @@ ], "time": "2026-03-04T13:55:34+00:00" }, - { - "name": "symfony/ux-turbo", - "version": "v2.34.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/ux-turbo.git", - "reference": "87511f621db238302a3bb819958a72feda27fc45" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/ux-turbo/zipball/87511f621db238302a3bb819958a72feda27fc45", - "reference": "87511f621db238302a3bb819958a72feda27fc45", - "shasum": "" - }, - "require": { - "php": ">=8.1", - "symfony/stimulus-bundle": "^2.9.1" - }, - "conflict": { - "symfony/flex": "<1.13" - }, - "require-dev": { - "dbrekelmans/bdi": "dev-main", - "doctrine/doctrine-bundle": "^2.4.3|^3.0|^4.0", - "doctrine/orm": "^2.8|^3.0", - "php-webdriver/webdriver": "^1.15", - "phpstan/phpstan": "^2.1.17", - "symfony/asset-mapper": "^6.4|^7.0|^8.0", - "symfony/debug-bundle": "^5.4|^6.0|^7.0|^8.0", - "symfony/expression-language": "^5.4|^6.0|^7.0|^8.0", - "symfony/form": "^5.4|^6.0|^7.0|^8.0", - "symfony/framework-bundle": "^6.4|^7.0|^8.0", - "symfony/mercure-bundle": "^0.3.7|^0.4.1", - "symfony/messenger": "^5.4|^6.0|^7.0|^8.0", - "symfony/panther": "^2.2", - "symfony/phpunit-bridge": "^5.4|^6.0|^7.0|^8.0", - "symfony/process": "^5.4|6.3.*|^7.0|^8.0", - "symfony/property-access": "^5.4|^6.0|^7.0|^8.0", - "symfony/security-core": "^5.4|^6.0|^7.0|^8.0", - "symfony/stopwatch": "^5.4|^6.0|^7.0|^8.0", - "symfony/twig-bundle": "^6.4|^7.0|^8.0", - "symfony/ux-twig-component": "^2.21", - "symfony/web-profiler-bundle": "^5.4|^6.0|^7.0|^8.0" - }, - "type": "symfony-bundle", - "extra": { - "thanks": { - "url": "https://github.com/symfony/ux", - "name": "symfony/ux" - } - }, - "autoload": { - "psr-4": { - "Symfony\\UX\\Turbo\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Kévin Dunglas", - "email": "kevin@dunglas.fr" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Hotwire Turbo integration for Symfony", - "homepage": "https://symfony.com", - "keywords": [ - "hotwire", - "javascript", - "mercure", - "symfony-ux", - "turbo", - "turbo-stream" - ], - "support": { - "source": "https://github.com/symfony/ux-turbo/tree/v2.34.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2026-03-21T22:29:11+00:00" - }, { "name": "symfony/validator", "version": "v8.0.7", @@ -9389,6 +10596,3868 @@ ], "time": "2026-01-01T23:07:29+00:00" }, + { + "name": "symfony/yaml", + "version": "v8.0.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "5f006c50a981e1630bbb70ad409c5d85f9a716e0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/5f006c50a981e1630bbb70ad409c5d85f9a716e0", + "reference": "5f006c50a981e1630bbb70ad409c5d85f9a716e0", + "shasum": "" + }, + "require": { + "php": ">=8.4", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "symfony/console": "<7.4" + }, + "require-dev": { + "symfony/console": "^7.4|^8.0" + }, + "bin": [ + "Resources/bin/yaml-lint" + ], + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Loads and dumps YAML files", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/yaml/tree/v8.0.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-02-09T10:14:57+00:00" + }, + { + "name": "thecodingmachine/safe", + "version": "v3.4.0", + "source": { + "type": "git", + "url": "https://github.com/thecodingmachine/safe.git", + "reference": "705683a25bacf0d4860c7dea4d7947bfd09eea19" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thecodingmachine/safe/zipball/705683a25bacf0d4860c7dea4d7947bfd09eea19", + "reference": "705683a25bacf0d4860c7dea4d7947bfd09eea19", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "php-parallel-lint/php-parallel-lint": "^1.4", + "phpstan/phpstan": "^2", + "phpunit/phpunit": "^10", + "squizlabs/php_codesniffer": "^3.2" + }, + "type": "library", + "autoload": { + "files": [ + "lib/special_cases.php", + "generated/apache.php", + "generated/apcu.php", + "generated/array.php", + "generated/bzip2.php", + "generated/calendar.php", + "generated/classobj.php", + "generated/com.php", + "generated/cubrid.php", + "generated/curl.php", + "generated/datetime.php", + "generated/dir.php", + "generated/eio.php", + "generated/errorfunc.php", + "generated/exec.php", + "generated/fileinfo.php", + "generated/filesystem.php", + "generated/filter.php", + "generated/fpm.php", + "generated/ftp.php", + "generated/funchand.php", + "generated/gettext.php", + "generated/gmp.php", + "generated/gnupg.php", + "generated/hash.php", + "generated/ibase.php", + "generated/ibmDb2.php", + "generated/iconv.php", + "generated/image.php", + "generated/imap.php", + "generated/info.php", + "generated/inotify.php", + "generated/json.php", + "generated/ldap.php", + "generated/libxml.php", + "generated/lzf.php", + "generated/mailparse.php", + "generated/mbstring.php", + "generated/misc.php", + "generated/mysql.php", + "generated/mysqli.php", + "generated/network.php", + "generated/oci8.php", + "generated/opcache.php", + "generated/openssl.php", + "generated/outcontrol.php", + "generated/pcntl.php", + "generated/pcre.php", + "generated/pgsql.php", + "generated/posix.php", + "generated/ps.php", + "generated/pspell.php", + "generated/readline.php", + "generated/rnp.php", + "generated/rpminfo.php", + "generated/rrd.php", + "generated/sem.php", + "generated/session.php", + "generated/shmop.php", + "generated/sockets.php", + "generated/sodium.php", + "generated/solr.php", + "generated/spl.php", + "generated/sqlsrv.php", + "generated/ssdeep.php", + "generated/ssh2.php", + "generated/stream.php", + "generated/strings.php", + "generated/swoole.php", + "generated/uodbc.php", + "generated/uopz.php", + "generated/url.php", + "generated/var.php", + "generated/xdiff.php", + "generated/xml.php", + "generated/xmlrpc.php", + "generated/yaml.php", + "generated/yaz.php", + "generated/zip.php", + "generated/zlib.php" + ], + "classmap": [ + "lib/DateTime.php", + "lib/DateTimeImmutable.php", + "lib/Exceptions/", + "generated/Exceptions/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHP core functions that throw exceptions instead of returning FALSE on error", + "support": { + "issues": "https://github.com/thecodingmachine/safe/issues", + "source": "https://github.com/thecodingmachine/safe/tree/v3.4.0" + }, + "funding": [ + { + "url": "https://github.com/OskarStark", + "type": "github" + }, + { + "url": "https://github.com/shish", + "type": "github" + }, + { + "url": "https://github.com/silasjoisten", + "type": "github" + }, + { + "url": "https://github.com/staabm", + "type": "github" + } + ], + "time": "2026-02-04T18:08:13+00:00" + }, + { + "name": "tijsverkoyen/css-to-inline-styles", + "version": "v2.4.0", + "source": { + "type": "git", + "url": "https://github.com/tijsverkoyen/CssToInlineStyles.git", + "reference": "f0292ccf0ec75843d65027214426b6b163b48b41" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/tijsverkoyen/CssToInlineStyles/zipball/f0292ccf0ec75843d65027214426b6b163b48b41", + "reference": "f0292ccf0ec75843d65027214426b6b163b48b41", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "php": "^7.4 || ^8.0", + "symfony/css-selector": "^5.4 || ^6.0 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^8.5.21 || ^9.5.10" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "TijsVerkoyen\\CssToInlineStyles\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Tijs Verkoyen", + "email": "css_to_inline_styles@verkoyen.eu", + "role": "Developer" + } + ], + "description": "CssToInlineStyles is a class that enables you to convert HTML-pages/files into HTML-pages/files with inline styles. This is very useful when you're sending emails.", + "homepage": "https://github.com/tijsverkoyen/CssToInlineStyles", + "support": { + "issues": "https://github.com/tijsverkoyen/CssToInlineStyles/issues", + "source": "https://github.com/tijsverkoyen/CssToInlineStyles/tree/v2.4.0" + }, + "time": "2025-12-02T11:56:42+00:00" + }, + { + "name": "twig/cssinliner-extra", + "version": "v3.24.0", + "source": { + "type": "git", + "url": "https://github.com/twigphp/cssinliner-extra.git", + "reference": "c25fa18b09a418e4d1454ec291f9406f630675ba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/twigphp/cssinliner-extra/zipball/c25fa18b09a418e4d1454ec291f9406f630675ba", + "reference": "c25fa18b09a418e4d1454ec291f9406f630675ba", + "shasum": "" + }, + "require": { + "php": ">=8.1.0", + "symfony/deprecation-contracts": "^2.5|^3", + "tijsverkoyen/css-to-inline-styles": "^2.0", + "twig/twig": "^3.13|^4.0" + }, + "require-dev": { + "symfony/phpunit-bridge": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Twig\\Extra\\CssInliner\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + } + ], + "description": "A Twig extension to allow inlining CSS", + "homepage": "https://twig.symfony.com", + "keywords": [ + "css", + "inlining", + "twig" + ], + "support": { + "source": "https://github.com/twigphp/cssinliner-extra/tree/v3.24.0" + }, + "funding": [ + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/twig/twig", + "type": "tidelift" + } + ], + "time": "2025-12-02T14:45:16+00:00" + }, + { + "name": "twig/extra-bundle", + "version": "v3.24.0", + "source": { + "type": "git", + "url": "https://github.com/twigphp/twig-extra-bundle.git", + "reference": "6a621fcb1f28aa9ea7b34a99047ae0cdf5b834c9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/twigphp/twig-extra-bundle/zipball/6a621fcb1f28aa9ea7b34a99047ae0cdf5b834c9", + "reference": "6a621fcb1f28aa9ea7b34a99047ae0cdf5b834c9", + "shasum": "" + }, + "require": { + "php": ">=8.1.0", + "symfony/framework-bundle": "^5.4|^6.4|^7.0|^8.0", + "symfony/twig-bundle": "^5.4|^6.4|^7.0|^8.0", + "twig/twig": "^3.2|^4.0" + }, + "require-dev": { + "league/commonmark": "^2.7", + "symfony/phpunit-bridge": "^6.4|^7.0", + "twig/cache-extra": "^3.0", + "twig/cssinliner-extra": "^3.0", + "twig/html-extra": "^3.0", + "twig/inky-extra": "^3.0", + "twig/intl-extra": "^3.0", + "twig/markdown-extra": "^3.0", + "twig/string-extra": "^3.0" + }, + "type": "symfony-bundle", + "autoload": { + "psr-4": { + "Twig\\Extra\\TwigExtraBundle\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + } + ], + "description": "A Symfony bundle for extra Twig extensions", + "homepage": "https://twig.symfony.com", + "keywords": [ + "bundle", + "extra", + "twig" + ], + "support": { + "source": "https://github.com/twigphp/twig-extra-bundle/tree/v3.24.0" + }, + "funding": [ + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/twig/twig", + "type": "tidelift" + } + ], + "time": "2026-02-07T08:07:38+00:00" + }, + { + "name": "twig/inky-extra", + "version": "v3.24.0", + "source": { + "type": "git", + "url": "https://github.com/twigphp/inky-extra.git", + "reference": "6bdca65a38167f7bd0ad7ea04819098d465a5cc4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/twigphp/inky-extra/zipball/6bdca65a38167f7bd0ad7ea04819098d465a5cc4", + "reference": "6bdca65a38167f7bd0ad7ea04819098d465a5cc4", + "shasum": "" + }, + "require": { + "lorenzo/pinky": "^1.0.5", + "php": ">=8.1.0", + "symfony/deprecation-contracts": "^2.5|^3", + "twig/twig": "^3.13|^4.0" + }, + "require-dev": { + "symfony/phpunit-bridge": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Twig\\Extra\\Inky\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + } + ], + "description": "A Twig extension for the inky email templating engine", + "homepage": "https://twig.symfony.com", + "keywords": [ + "email", + "emails", + "inky", + "twig" + ], + "support": { + "source": "https://github.com/twigphp/inky-extra/tree/v3.24.0" + }, + "funding": [ + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/twig/twig", + "type": "tidelift" + } + ], + "time": "2025-12-02T14:45:16+00:00" + }, + { + "name": "twig/twig", + "version": "v3.24.0", + "source": { + "type": "git", + "url": "https://github.com/twigphp/Twig.git", + "reference": "a6769aefb305efef849dc25c9fd1653358c148f0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/a6769aefb305efef849dc25c9fd1653358c148f0", + "reference": "a6769aefb305efef849dc25c9fd1653358c148f0", + "shasum": "" + }, + "require": { + "php": ">=8.1.0", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-ctype": "^1.8", + "symfony/polyfill-mbstring": "^1.3" + }, + "require-dev": { + "php-cs-fixer/shim": "^3.0@stable", + "phpstan/phpstan": "^2.0@stable", + "psr/container": "^1.0|^2.0", + "symfony/phpunit-bridge": "^5.4.9|^6.4|^7.0" + }, + "type": "library", + "autoload": { + "files": [ + "src/Resources/core.php", + "src/Resources/debug.php", + "src/Resources/escaper.php", + "src/Resources/string_loader.php" + ], + "psr-4": { + "Twig\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + }, + { + "name": "Twig Team", + "role": "Contributors" + }, + { + "name": "Armin Ronacher", + "email": "armin.ronacher@active-4.com", + "role": "Project Founder" + } + ], + "description": "Twig, the flexible, fast, and secure template language for PHP", + "homepage": "https://twig.symfony.com", + "keywords": [ + "templating" + ], + "support": { + "issues": "https://github.com/twigphp/Twig/issues", + "source": "https://github.com/twigphp/Twig/tree/v3.24.0" + }, + "funding": [ + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/twig/twig", + "type": "tidelift" + } + ], + "time": "2026-03-17T21:31:11+00:00" + }, + { + "name": "ua-parser/uap-php", + "version": "v3.10.0", + "source": { + "type": "git", + "url": "https://github.com/ua-parser/uap-php.git", + "reference": "f44bdd1b38198801cf60b0681d2d842980e47af5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ua-parser/uap-php/zipball/f44bdd1b38198801cf60b0681d2d842980e47af5", + "reference": "f44bdd1b38198801cf60b0681d2d842980e47af5", + "shasum": "" + }, + "require": { + "composer/ca-bundle": "^1.1", + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^0.12.33", + "phpunit/phpunit": "^8 || ^9", + "symfony/console": "^3.4 || ^4.2 || ^4.3 || ^5.0", + "symfony/filesystem": "^3.4 || ^4.2 || ^4.3 || ^5.0", + "symfony/finder": "^3.4 || ^4.2 || ^4.3 || ^5.0", + "symfony/yaml": "^3.4 || ^4.2 || ^4.3 || ^5.0", + "vimeo/psalm": "^3.12" + }, + "suggest": { + "symfony/console": "Required for CLI usage - ^3.4 || ^4.3 || ^5.0", + "symfony/filesystem": "Required for CLI usage - ^3.4 || ^4.3 || ^5.0", + "symfony/finder": "Required for CLI usage - ^3.4 || ^4.3 || ^5.0", + "symfony/yaml": "Required for CLI usage - ^3.4 || ^4.3 || ^5.0" + }, + "bin": [ + "bin/uaparser" + ], + "type": "library", + "autoload": { + "psr-4": { + "UAParser\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Dave Olsen", + "email": "dmolsen@gmail.com" + }, + { + "name": "Lars Strojny", + "email": "lars@strojny.net" + } + ], + "description": "A multi-language port of Browserscope's user agent parser.", + "support": { + "issues": "https://github.com/ua-parser/uap-php/issues", + "source": "https://github.com/ua-parser/uap-php/tree/v3.10.0" + }, + "time": "2025-07-17T15:43:24+00:00" + }, + { + "name": "vich/uploader-bundle", + "version": "v2.9.2", + "source": { + "type": "git", + "url": "https://github.com/dustin10/VichUploaderBundle.git", + "reference": "4b88b23cb859a121413df4a831d960a46d6fe27f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dustin10/VichUploaderBundle/zipball/4b88b23cb859a121413df4a831d960a46d6fe27f", + "reference": "4b88b23cb859a121413df4a831d960a46d6fe27f", + "shasum": "" + }, + "require": { + "doctrine/persistence": "^3.0 || ^4.0", + "ext-simplexml": "*", + "jms/metadata": "^2.4", + "php": "^8.1", + "symfony/config": "^5.4 || ^6.0 || ^7.0 || ^8.0", + "symfony/console": "^5.4 || ^6.0 || ^7.0 || ^8.0", + "symfony/dependency-injection": "^5.4 || ^6.0 || ^7.0 || ^8.0", + "symfony/event-dispatcher-contracts": "^3.1", + "symfony/http-foundation": "^5.4 || ^6.0 || ^7.0 || ^8.0", + "symfony/http-kernel": "^5.4 || ^6.0 || ^7.0 || ^8.0", + "symfony/mime": "^5.4 || ^6.0 || ^7.0 || ^8.0", + "symfony/property-access": "^5.4 || ^6.0 || ^7.0 || ^8.0", + "symfony/string": "^5.4 || ^6.0 || ^7.0 || ^8.0" + }, + "conflict": { + "doctrine/annotations": "<1.12", + "league/flysystem": "<2.0" + }, + "require-dev": { + "dg/bypass-finals": "^1.9", + "doctrine/common": "^3.0", + "doctrine/doctrine-bundle": "^2.7 || ^3.0", + "doctrine/mongodb-odm": "^2.4", + "doctrine/orm": "^2.13 || ^3.0", + "ext-sqlite3": "*", + "knplabs/knp-gaufrette-bundle": "dev-master", + "league/flysystem-bundle": "^2.4 || ^3.0", + "league/flysystem-memory": "^2.0 || ^3.0", + "matthiasnoback/symfony-dependency-injection-test": "^5.1 || ^6.0", + "mikey179/vfsstream": "^1.6.11", + "phpunit/phpunit": "^10.5 || ^11.5 || ^12.2", + "symfony/asset": "^5.4 || ^6.0 || ^7.0 || ^8.0", + "symfony/browser-kit": "^5.4 || ^6.0 || ^7.0 || ^8.0", + "symfony/css-selector": "^5.4 || ^6.0 || ^7.0 || ^8.0", + "symfony/doctrine-bridge": "^5.4 || ^6.0 || ^7.0 || ^8.0", + "symfony/dom-crawler": "^5.4 || ^6.0 || ^7.0 || ^8.0", + "symfony/form": "^5.4 || ^6.0 || ^7.0 || ^8.0", + "symfony/framework-bundle": "^5.4 || ^6.0 || ^7.0 || ^8.0", + "symfony/phpunit-bridge": "^7.3", + "symfony/security-csrf": "^5.4 || ^6.0 || ^7.0 || ^8.0", + "symfony/translation": "^5.4 || ^6.0 || ^7.0 || ^8.0", + "symfony/twig-bridge": "^5.4 || ^6.0 || ^7.0 || ^8.0", + "symfony/twig-bundle": "^5.4 || ^6.0 || ^7.0 || ^8.0", + "symfony/validator": "^5.4.22 || ^6.0 || ^7.0 || ^8.0", + "symfony/var-dumper": "^5.4 || ^6.0 || ^7.0 || ^8.0", + "symfony/yaml": "^5.4 || ^6.0 || ^7.0 || ^8.0" + }, + "suggest": { + "doctrine/doctrine-bundle": "For integration with Doctrine", + "doctrine/mongodb-odm-bundle": "For integration with Doctrine ODM", + "doctrine/orm": "For integration with Doctrine ORM", + "doctrine/phpcr-odm": "For integration with Doctrine PHPCR", + "knplabs/knp-gaufrette-bundle": "For integration with Gaufrette", + "league/flysystem-bundle": "For integration with Flysystem", + "liip/imagine-bundle": "To generate image thumbnails", + "oneup/flysystem-bundle": "For integration with Flysystem", + "symfony/asset": "To generate better links", + "symfony/form": "To handle uploads in forms", + "symfony/yaml": "To use YAML mapping" + }, + "type": "symfony-bundle", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Vich\\UploaderBundle\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Dustin Dobervich", + "email": "ddobervich@gmail.com" + } + ], + "description": "Ease file uploads attached to entities", + "homepage": "https://github.com/dustin10/VichUploaderBundle", + "keywords": [ + "file uploads", + "upload" + ], + "support": { + "issues": "https://github.com/dustin10/VichUploaderBundle/issues", + "source": "https://github.com/dustin10/VichUploaderBundle/tree/v2.9.2" + }, + "time": "2026-03-12T08:18:50+00:00" + }, + { + "name": "webmozart/assert", + "version": "2.1.6", + "source": { + "type": "git", + "url": "https://github.com/webmozarts/assert.git", + "reference": "ff31ad6efc62e66e518fbab1cde3453d389bcdc8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/ff31ad6efc62e66e518fbab1cde3453d389bcdc8", + "reference": "ff31ad6efc62e66e518fbab1cde3453d389bcdc8", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-date": "*", + "ext-filter": "*", + "php": "^8.2" + }, + "suggest": { + "ext-intl": "", + "ext-simplexml": "", + "ext-spl": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-feature/2-0": "2.0-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + }, + { + "name": "Woody Gilk", + "email": "woody.gilk@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "support": { + "issues": "https://github.com/webmozarts/assert/issues", + "source": "https://github.com/webmozarts/assert/tree/2.1.6" + }, + "time": "2026-02-27T10:28:38+00:00" + } + ], + "packages-dev": [ + { + "name": "clue/ndjson-react", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/clue/reactphp-ndjson.git", + "reference": "392dc165fce93b5bb5c637b67e59619223c931b0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/clue/reactphp-ndjson/zipball/392dc165fce93b5bb5c637b67e59619223c931b0", + "reference": "392dc165fce93b5bb5c637b67e59619223c931b0", + "shasum": "" + }, + "require": { + "php": ">=5.3", + "react/stream": "^1.2" + }, + "require-dev": { + "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35", + "react/event-loop": "^1.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Clue\\React\\NDJson\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering" + } + ], + "description": "Streaming newline-delimited JSON (NDJSON) parser and encoder for ReactPHP.", + "homepage": "https://github.com/clue/reactphp-ndjson", + "keywords": [ + "NDJSON", + "json", + "jsonlines", + "newline", + "reactphp", + "streaming" + ], + "support": { + "issues": "https://github.com/clue/reactphp-ndjson/issues", + "source": "https://github.com/clue/reactphp-ndjson/tree/v1.3.0" + }, + "funding": [ + { + "url": "https://clue.engineering/support", + "type": "custom" + }, + { + "url": "https://github.com/clue", + "type": "github" + } + ], + "time": "2022-12-23T10:58:28+00:00" + }, + { + "name": "composer/pcre", + "version": "3.3.2", + "source": { + "type": "git", + "url": "https://github.com/composer/pcre.git", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/pcre/zipball/b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "conflict": { + "phpstan/phpstan": "<1.11.10" + }, + "require-dev": { + "phpstan/phpstan": "^1.12 || ^2", + "phpstan/phpstan-strict-rules": "^1 || ^2", + "phpunit/phpunit": "^8 || ^9" + }, + "type": "library", + "extra": { + "phpstan": { + "includes": [ + "extension.neon" + ] + }, + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Pcre\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "PCRE wrapping library that offers type-safe preg_* replacements.", + "keywords": [ + "PCRE", + "preg", + "regex", + "regular expression" + ], + "support": { + "issues": "https://github.com/composer/pcre/issues", + "source": "https://github.com/composer/pcre/tree/3.3.2" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-11-12T16:29:46+00:00" + }, + { + "name": "composer/xdebug-handler", + "version": "3.0.5", + "source": { + "type": "git", + "url": "https://github.com/composer/xdebug-handler.git", + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/6c1925561632e83d60a44492e0b344cf48ab85ef", + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef", + "shasum": "" + }, + "require": { + "composer/pcre": "^1 || ^2 || ^3", + "php": "^7.2.5 || ^8.0", + "psr/log": "^1 || ^2 || ^3" + }, + "require-dev": { + "phpstan/phpstan": "^1.0", + "phpstan/phpstan-strict-rules": "^1.1", + "phpunit/phpunit": "^8.5 || ^9.6 || ^10.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Composer\\XdebugHandler\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "John Stevenson", + "email": "john-stevenson@blueyonder.co.uk" + } + ], + "description": "Restarts a process without Xdebug.", + "keywords": [ + "Xdebug", + "performance" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/xdebug-handler/issues", + "source": "https://github.com/composer/xdebug-handler/tree/3.0.5" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-05-06T16:37:16+00:00" + }, + { + "name": "evenement/evenement", + "version": "v3.0.2", + "source": { + "type": "git", + "url": "https://github.com/igorw/evenement.git", + "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/igorw/evenement/zipball/0a16b0d71ab13284339abb99d9d2bd813640efbc", + "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc", + "shasum": "" + }, + "require": { + "php": ">=7.0" + }, + "require-dev": { + "phpunit/phpunit": "^9 || ^6" + }, + "type": "library", + "autoload": { + "psr-4": { + "Evenement\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" + } + ], + "description": "Événement is a very simple event dispatching library for PHP", + "keywords": [ + "event-dispatcher", + "event-emitter" + ], + "support": { + "issues": "https://github.com/igorw/evenement/issues", + "source": "https://github.com/igorw/evenement/tree/v3.0.2" + }, + "time": "2023-08-08T05:53:35+00:00" + }, + { + "name": "fidry/cpu-core-counter", + "version": "1.3.0", + "source": { + "type": "git", + "url": "https://github.com/theofidry/cpu-core-counter.git", + "reference": "db9508f7b1474469d9d3c53b86f817e344732678" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/db9508f7b1474469d9d3c53b86f817e344732678", + "reference": "db9508f7b1474469d9d3c53b86f817e344732678", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "fidry/makefile": "^0.2.0", + "fidry/php-cs-fixer-config": "^1.1.2", + "phpstan/extension-installer": "^1.2.0", + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-deprecation-rules": "^2.0.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^8.5.31 || ^9.5.26", + "webmozarts/strict-phpunit": "^7.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Fidry\\CpuCoreCounter\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Théo FIDRY", + "email": "theo.fidry@gmail.com" + } + ], + "description": "Tiny utility to get the number of CPU cores.", + "keywords": [ + "CPU", + "core" + ], + "support": { + "issues": "https://github.com/theofidry/cpu-core-counter/issues", + "source": "https://github.com/theofidry/cpu-core-counter/tree/1.3.0" + }, + "funding": [ + { + "url": "https://github.com/theofidry", + "type": "github" + } + ], + "time": "2025-08-14T07:29:31+00:00" + }, + { + "name": "friendsofphp/php-cs-fixer", + "version": "v3.94.2", + "source": { + "type": "git", + "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", + "reference": "7787ceff91365ba7d623ec410b8f429cdebb4f63" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/7787ceff91365ba7d623ec410b8f429cdebb4f63", + "reference": "7787ceff91365ba7d623ec410b8f429cdebb4f63", + "shasum": "" + }, + "require": { + "clue/ndjson-react": "^1.3", + "composer/semver": "^3.4", + "composer/xdebug-handler": "^3.0.5", + "ext-filter": "*", + "ext-hash": "*", + "ext-json": "*", + "ext-tokenizer": "*", + "fidry/cpu-core-counter": "^1.3", + "php": "^7.4 || ^8.0", + "react/child-process": "^0.6.6", + "react/event-loop": "^1.5", + "react/socket": "^1.16", + "react/stream": "^1.4", + "sebastian/diff": "^4.0.6 || ^5.1.1 || ^6.0.2 || ^7.0 || ^8.0", + "symfony/console": "^5.4.47 || ^6.4.24 || ^7.0 || ^8.0", + "symfony/event-dispatcher": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0", + "symfony/filesystem": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0", + "symfony/finder": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0", + "symfony/options-resolver": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0", + "symfony/polyfill-mbstring": "^1.33", + "symfony/polyfill-php80": "^1.33", + "symfony/polyfill-php81": "^1.33", + "symfony/polyfill-php84": "^1.33", + "symfony/process": "^5.4.47 || ^6.4.24 || ^7.2 || ^8.0", + "symfony/stopwatch": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0" + }, + "require-dev": { + "facile-it/paraunit": "^1.3.1 || ^2.7.1", + "infection/infection": "^0.32.3", + "justinrainbow/json-schema": "^6.6.4", + "keradus/cli-executor": "^2.3", + "mikey179/vfsstream": "^1.6.12", + "php-coveralls/php-coveralls": "^2.9.1", + "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.7", + "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.7", + "phpunit/phpunit": "^9.6.34 || ^10.5.63 || ^11.5.51", + "symfony/polyfill-php85": "^1.33", + "symfony/var-dumper": "^5.4.48 || ^6.4.32 || ^7.4.4 || ^8.0.4", + "symfony/yaml": "^5.4.45 || ^6.4.30 || ^7.4.1 || ^8.0.1" + }, + "suggest": { + "ext-dom": "For handling output formats in XML", + "ext-mbstring": "For handling non-UTF8 characters." + }, + "bin": [ + "php-cs-fixer" + ], + "type": "application", + "autoload": { + "psr-4": { + "PhpCsFixer\\": "src/" + }, + "exclude-from-classmap": [ + "src/**/Internal/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Dariusz Rumiński", + "email": "dariusz.ruminski@gmail.com" + } + ], + "description": "A tool to automatically fix PHP code style", + "keywords": [ + "Static code analysis", + "fixer", + "standards", + "static analysis" + ], + "support": { + "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues", + "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.94.2" + }, + "funding": [ + { + "url": "https://github.com/keradus", + "type": "github" + } + ], + "time": "2026-02-20T16:13:53+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.13.4", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3 <3.2.2" + }, + "require-dev": { + "doctrine/collections": "^1.6.8", + "doctrine/common": "^2.13.3 || ^3.2.2", + "phpspec/prophecy": "^1.10", + "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" + }, + "type": "library", + "autoload": { + "files": [ + "src/DeepCopy/deep_copy.php" + ], + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2025-08-01T08:46:24+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v5.7.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/dca41cd15c2ac9d055ad70dbfd011130757d1f82", + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-json": "*", + "ext-tokenizer": "*", + "php": ">=7.4" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v5.7.0" + }, + "time": "2025-12-06T11:56:16+00:00" + }, + { + "name": "phar-io/manifest", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "54750ef60c58e43759730615a392c31c80e23176" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", + "reference": "54750ef60c58e43759730615a392c31c80e23176", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-phar": "*", + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "support": { + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:33:53+00:00" + }, + { + "name": "phar-io/version", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "support": { + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.2.1" + }, + "time": "2022-02-21T01:04:05+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "2.1.45", + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/f8cdfd9421b7edb7686a2d150a234870464eac70", + "reference": "f8cdfd9421b7edb7686a2d150a234870464eac70", + "shasum": "" + }, + "require": { + "php": "^7.4|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "keywords": [ + "dev", + "static analysis" + ], + "support": { + "docs": "https://phpstan.org/user-guide/getting-started", + "forum": "https://github.com/phpstan/phpstan/discussions", + "issues": "https://github.com/phpstan/phpstan/issues", + "security": "https://github.com/phpstan/phpstan/security/policy", + "source": "https://github.com/phpstan/phpstan-src" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + } + ], + "time": "2026-03-30T13:22:02+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "13.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "a8b58fde2f4fbc69a064e1f80ff917607cf7737c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/a8b58fde2f4fbc69a064e1f80ff917607cf7737c", + "reference": "a8b58fde2f4fbc69a064e1f80ff917607cf7737c", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^5.7.0", + "php": ">=8.4", + "phpunit/php-file-iterator": "^7.0", + "phpunit/php-text-template": "^6.0", + "sebastian/complexity": "^6.0", + "sebastian/environment": "^9.0", + "sebastian/lines-of-code": "^5.0", + "sebastian/version": "^7.0", + "theseer/tokenizer": "^2.0.1" + }, + "require-dev": { + "phpunit/phpunit": "^13.0" + }, + "suggest": { + "ext-pcov": "PHP extension that provides line coverage", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "13.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/13.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/php-code-coverage", + "type": "tidelift" + } + ], + "time": "2026-02-06T06:05:15+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "7.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "6e5aa1fb0a95b1703d83e721299ee18bb4e2de50" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/6e5aa1fb0a95b1703d83e721299ee18bb4e2de50", + "reference": "6e5aa1fb0a95b1703d83e721299ee18bb4e2de50", + "shasum": "" + }, + "require": { + "php": ">=8.4" + }, + "require-dev": { + "phpunit/phpunit": "^13.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/7.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/php-file-iterator", + "type": "tidelift" + } + ], + "time": "2026-02-06T04:33:26+00:00" + }, + { + "name": "phpunit/php-invoker", + "version": "7.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "42e5c5cae0c65df12d1b1a3ab52bf3f50f244d88" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/42e5c5cae0c65df12d1b1a3ab52bf3f50f244d88", + "reference": "42e5c5cae0c65df12d1b1a3ab52bf3f50f244d88", + "shasum": "" + }, + "require": { + "php": ">=8.4" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^13.0" + }, + "suggest": { + "ext-pcntl": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "keywords": [ + "process" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "security": "https://github.com/sebastianbergmann/php-invoker/security/policy", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/7.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/php-invoker", + "type": "tidelift" + } + ], + "time": "2026-02-06T04:34:47+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "6.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "a47af19f93f76aa3368303d752aa5272ca3299f4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/a47af19f93f76aa3368303d752aa5272ca3299f4", + "reference": "a47af19f93f76aa3368303d752aa5272ca3299f4", + "shasum": "" + }, + "require": { + "php": ">=8.4" + }, + "require-dev": { + "phpunit/phpunit": "^13.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "security": "https://github.com/sebastianbergmann/php-text-template/security/policy", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/6.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/php-text-template", + "type": "tidelift" + } + ], + "time": "2026-02-06T04:36:37+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "9.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "a0e12065831f6ab0d83120dc61513eb8d9a966f6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/a0e12065831f6ab0d83120dc61513eb8d9a966f6", + "reference": "a0e12065831f6ab0d83120dc61513eb8d9a966f6", + "shasum": "" + }, + "require": { + "php": ">=8.4" + }, + "require-dev": { + "phpunit/phpunit": "^13.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "9.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "security": "https://github.com/sebastianbergmann/php-timer/security/policy", + "source": "https://github.com/sebastianbergmann/php-timer/tree/9.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/php-timer", + "type": "tidelift" + } + ], + "time": "2026-02-06T04:37:53+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "13.0.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "d57826e8921a534680c613924bfd921ded8047f4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/d57826e8921a534680c613924bfd921ded8047f4", + "reference": "d57826e8921a534680c613924bfd921ded8047f4", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.13.4", + "phar-io/manifest": "^2.0.4", + "phar-io/version": "^3.2.1", + "php": ">=8.4.1", + "phpunit/php-code-coverage": "^13.0.1", + "phpunit/php-file-iterator": "^7.0.0", + "phpunit/php-invoker": "^7.0.0", + "phpunit/php-text-template": "^6.0.0", + "phpunit/php-timer": "^9.0.0", + "sebastian/cli-parser": "^5.0.0", + "sebastian/comparator": "^8.0.0", + "sebastian/diff": "^8.0.0", + "sebastian/environment": "^9.0.0", + "sebastian/exporter": "^8.0.0", + "sebastian/global-state": "^9.0.0", + "sebastian/object-enumerator": "^8.0.0", + "sebastian/recursion-context": "^8.0.0", + "sebastian/type": "^7.0.0", + "sebastian/version": "^7.0.0", + "staabm/side-effects-detector": "^1.0.5" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "13.0-dev" + } + }, + "autoload": { + "files": [ + "src/Framework/Assert/Functions.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "security": "https://github.com/sebastianbergmann/phpunit/security/policy", + "source": "https://github.com/sebastianbergmann/phpunit/tree/13.0.5" + }, + "funding": [ + { + "url": "https://phpunit.de/sponsors.html", + "type": "custom" + }, + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", + "type": "tidelift" + } + ], + "time": "2026-02-18T12:40:03+00:00" + }, + { + "name": "react/cache", + "version": "v1.2.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/cache.git", + "reference": "d47c472b64aa5608225f47965a484b75c7817d5b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/cache/zipball/d47c472b64aa5608225f47965a484b75c7817d5b", + "reference": "d47c472b64aa5608225f47965a484b75c7817d5b", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "react/promise": "^3.0 || ^2.0 || ^1.1" + }, + "require-dev": { + "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Async, Promise-based cache interface for ReactPHP", + "keywords": [ + "cache", + "caching", + "promise", + "reactphp" + ], + "support": { + "issues": "https://github.com/reactphp/cache/issues", + "source": "https://github.com/reactphp/cache/tree/v1.2.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2022-11-30T15:59:55+00:00" + }, + { + "name": "react/child-process", + "version": "v0.6.7", + "source": { + "type": "git", + "url": "https://github.com/reactphp/child-process.git", + "reference": "970f0e71945556422ee4570ccbabaedc3cf04ad3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/child-process/zipball/970f0e71945556422ee4570ccbabaedc3cf04ad3", + "reference": "970f0e71945556422ee4570ccbabaedc3cf04ad3", + "shasum": "" + }, + "require": { + "evenement/evenement": "^3.0 || ^2.0 || ^1.0", + "php": ">=5.3.0", + "react/event-loop": "^1.2", + "react/stream": "^1.4" + }, + "require-dev": { + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", + "react/socket": "^1.16", + "sebastian/environment": "^5.0 || ^3.0 || ^2.0 || ^1.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\ChildProcess\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Event-driven library for executing child processes with ReactPHP.", + "keywords": [ + "event-driven", + "process", + "reactphp" + ], + "support": { + "issues": "https://github.com/reactphp/child-process/issues", + "source": "https://github.com/reactphp/child-process/tree/v0.6.7" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2025-12-23T15:25:20+00:00" + }, + { + "name": "react/dns", + "version": "v1.14.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/dns.git", + "reference": "7562c05391f42701c1fccf189c8225fece1cd7c3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/dns/zipball/7562c05391f42701c1fccf189c8225fece1cd7c3", + "reference": "7562c05391f42701c1fccf189c8225fece1cd7c3", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "react/cache": "^1.0 || ^0.6 || ^0.5", + "react/event-loop": "^1.2", + "react/promise": "^3.2 || ^2.7 || ^1.2.1" + }, + "require-dev": { + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", + "react/async": "^4.3 || ^3 || ^2", + "react/promise-timer": "^1.11" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Dns\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Async DNS resolver for ReactPHP", + "keywords": [ + "async", + "dns", + "dns-resolver", + "reactphp" + ], + "support": { + "issues": "https://github.com/reactphp/dns/issues", + "source": "https://github.com/reactphp/dns/tree/v1.14.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2025-11-18T19:34:28+00:00" + }, + { + "name": "react/event-loop", + "version": "v1.6.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/event-loop.git", + "reference": "ba276bda6083df7e0050fd9b33f66ad7a4ac747a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/event-loop/zipball/ba276bda6083df7e0050fd9b33f66ad7a4ac747a", + "reference": "ba276bda6083df7e0050fd9b33f66ad7a4ac747a", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" + }, + "suggest": { + "ext-pcntl": "For signal handling support when using the StreamSelectLoop" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\EventLoop\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "ReactPHP's core reactor event loop that libraries can use for evented I/O.", + "keywords": [ + "asynchronous", + "event-loop" + ], + "support": { + "issues": "https://github.com/reactphp/event-loop/issues", + "source": "https://github.com/reactphp/event-loop/tree/v1.6.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2025-11-17T20:46:25+00:00" + }, + { + "name": "react/promise", + "version": "v3.3.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/promise.git", + "reference": "23444f53a813a3296c1368bb104793ce8d88f04a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/promise/zipball/23444f53a813a3296c1368bb104793ce8d88f04a", + "reference": "23444f53a813a3296c1368bb104793ce8d88f04a", + "shasum": "" + }, + "require": { + "php": ">=7.1.0" + }, + "require-dev": { + "phpstan/phpstan": "1.12.28 || 1.4.10", + "phpunit/phpunit": "^9.6 || ^7.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "React\\Promise\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "A lightweight implementation of CommonJS Promises/A for PHP", + "keywords": [ + "promise", + "promises" + ], + "support": { + "issues": "https://github.com/reactphp/promise/issues", + "source": "https://github.com/reactphp/promise/tree/v3.3.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2025-08-19T18:57:03+00:00" + }, + { + "name": "react/socket", + "version": "v1.17.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/socket.git", + "reference": "ef5b17b81f6f60504c539313f94f2d826c5faa08" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/socket/zipball/ef5b17b81f6f60504c539313f94f2d826c5faa08", + "reference": "ef5b17b81f6f60504c539313f94f2d826c5faa08", + "shasum": "" + }, + "require": { + "evenement/evenement": "^3.0 || ^2.0 || ^1.0", + "php": ">=5.3.0", + "react/dns": "^1.13", + "react/event-loop": "^1.2", + "react/promise": "^3.2 || ^2.6 || ^1.2.1", + "react/stream": "^1.4" + }, + "require-dev": { + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", + "react/async": "^4.3 || ^3.3 || ^2", + "react/promise-stream": "^1.4", + "react/promise-timer": "^1.11" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Socket\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Async, streaming plaintext TCP/IP and secure TLS socket server and client connections for ReactPHP", + "keywords": [ + "Connection", + "Socket", + "async", + "reactphp", + "stream" + ], + "support": { + "issues": "https://github.com/reactphp/socket/issues", + "source": "https://github.com/reactphp/socket/tree/v1.17.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2025-11-19T20:47:34+00:00" + }, + { + "name": "react/stream", + "version": "v1.4.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/stream.git", + "reference": "1e5b0acb8fe55143b5b426817155190eb6f5b18d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/stream/zipball/1e5b0acb8fe55143b5b426817155190eb6f5b18d", + "reference": "1e5b0acb8fe55143b5b426817155190eb6f5b18d", + "shasum": "" + }, + "require": { + "evenement/evenement": "^3.0 || ^2.0 || ^1.0", + "php": ">=5.3.8", + "react/event-loop": "^1.2" + }, + "require-dev": { + "clue/stream-filter": "~1.2", + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Stream\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Event-driven readable and writable streams for non-blocking I/O in ReactPHP", + "keywords": [ + "event-driven", + "io", + "non-blocking", + "pipe", + "reactphp", + "readable", + "stream", + "writable" + ], + "support": { + "issues": "https://github.com/reactphp/stream/issues", + "source": "https://github.com/reactphp/stream/tree/v1.4.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2024-06-11T12:45:25+00:00" + }, + { + "name": "sebastian/cli-parser", + "version": "5.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "48a4654fa5e48c1c81214e9930048a572d4b23ca" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/48a4654fa5e48c1c81214e9930048a572d4b23ca", + "reference": "48a4654fa5e48c1c81214e9930048a572d4b23ca", + "shasum": "" + }, + "require": { + "php": ">=8.4" + }, + "require-dev": { + "phpunit/phpunit": "^13.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for parsing CLI options", + "homepage": "https://github.com/sebastianbergmann/cli-parser", + "support": { + "issues": "https://github.com/sebastianbergmann/cli-parser/issues", + "security": "https://github.com/sebastianbergmann/cli-parser/security/policy", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/5.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/cli-parser", + "type": "tidelift" + } + ], + "time": "2026-02-06T04:39:44+00:00" + }, + { + "name": "sebastian/comparator", + "version": "8.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "29b232ddc29c2b114c0358c69b3084e7c3da0d58" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/29b232ddc29c2b114c0358c69b3084e7c3da0d58", + "reference": "29b232ddc29c2b114c0358c69b3084e7c3da0d58", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-mbstring": "*", + "php": ">=8.4", + "sebastian/diff": "^8.0", + "sebastian/exporter": "^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^13.0" + }, + "suggest": { + "ext-bcmath": "For comparing BcMath\\Number objects" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "8.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "security": "https://github.com/sebastianbergmann/comparator/security/policy", + "source": "https://github.com/sebastianbergmann/comparator/tree/8.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/comparator", + "type": "tidelift" + } + ], + "time": "2026-02-06T04:40:39+00:00" + }, + { + "name": "sebastian/complexity", + "version": "6.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "c5651c795c98093480df79350cb050813fc7a2f3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/c5651c795c98093480df79350cb050813fc7a2f3", + "reference": "c5651c795c98093480df79350cb050813fc7a2f3", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^5.0", + "php": ">=8.4" + }, + "require-dev": { + "phpunit/phpunit": "^13.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for calculating the complexity of PHP code units", + "homepage": "https://github.com/sebastianbergmann/complexity", + "support": { + "issues": "https://github.com/sebastianbergmann/complexity/issues", + "security": "https://github.com/sebastianbergmann/complexity/security/policy", + "source": "https://github.com/sebastianbergmann/complexity/tree/6.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/complexity", + "type": "tidelift" + } + ], + "time": "2026-02-06T04:41:32+00:00" + }, + { + "name": "sebastian/diff", + "version": "8.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "a2b6d09d7729ee87d605a439469f9dcc39be5ea3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/a2b6d09d7729ee87d605a439469f9dcc39be5ea3", + "reference": "a2b6d09d7729ee87d605a439469f9dcc39be5ea3", + "shasum": "" + }, + "require": { + "php": ">=8.4" + }, + "require-dev": { + "phpunit/phpunit": "^13.0", + "symfony/process": "^7.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "8.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "security": "https://github.com/sebastianbergmann/diff/security/policy", + "source": "https://github.com/sebastianbergmann/diff/tree/8.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/diff", + "type": "tidelift" + } + ], + "time": "2026-02-06T04:42:27+00:00" + }, + { + "name": "sebastian/environment", + "version": "9.1.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "c4a2dc54b1a24e13ef1839cbb5947b967cbae853" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/c4a2dc54b1a24e13ef1839cbb5947b967cbae853", + "reference": "c4a2dc54b1a24e13ef1839cbb5947b967cbae853", + "shasum": "" + }, + "require": { + "php": ">=8.4" + }, + "require-dev": { + "phpunit/phpunit": "^13.0" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "9.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "https://github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "security": "https://github.com/sebastianbergmann/environment/security/policy", + "source": "https://github.com/sebastianbergmann/environment/tree/9.1.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/environment", + "type": "tidelift" + } + ], + "time": "2026-03-22T06:31:50+00:00" + }, + { + "name": "sebastian/exporter", + "version": "8.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "dc31f1f8e0186c8f0bb3e48fd4d51421d8905fea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/dc31f1f8e0186c8f0bb3e48fd4d51421d8905fea", + "reference": "dc31f1f8e0186c8f0bb3e48fd4d51421d8905fea", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": ">=8.4", + "sebastian/recursion-context": "^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^13.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "8.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "https://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "security": "https://github.com/sebastianbergmann/exporter/security/policy", + "source": "https://github.com/sebastianbergmann/exporter/tree/8.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/exporter", + "type": "tidelift" + } + ], + "time": "2026-02-06T04:44:28+00:00" + }, + { + "name": "sebastian/global-state", + "version": "9.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "e52e3dc22441e6218c710afe72c3042f8fc41ea7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/e52e3dc22441e6218c710afe72c3042f8fc41ea7", + "reference": "e52e3dc22441e6218c710afe72c3042f8fc41ea7", + "shasum": "" + }, + "require": { + "php": ">=8.4", + "sebastian/object-reflector": "^6.0", + "sebastian/recursion-context": "^8.0" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^13.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "9.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "https://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "security": "https://github.com/sebastianbergmann/global-state/security/policy", + "source": "https://github.com/sebastianbergmann/global-state/tree/9.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/global-state", + "type": "tidelift" + } + ], + "time": "2026-02-06T04:45:13+00:00" + }, + { + "name": "sebastian/lines-of-code", + "version": "5.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "4f21bb7768e1c997722ccc7efb1d6b5c11bfd471" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/4f21bb7768e1c997722ccc7efb1d6b5c11bfd471", + "reference": "4f21bb7768e1c997722ccc7efb1d6b5c11bfd471", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^5.0", + "php": ">=8.4" + }, + "require-dev": { + "phpunit/phpunit": "^13.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for counting the lines of code in PHP source code", + "homepage": "https://github.com/sebastianbergmann/lines-of-code", + "support": { + "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", + "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/5.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/lines-of-code", + "type": "tidelift" + } + ], + "time": "2026-02-06T04:45:54+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "8.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "b39ab125fd9a7434b0ecbc4202eebce11a98cfc5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/b39ab125fd9a7434b0ecbc4202eebce11a98cfc5", + "reference": "b39ab125fd9a7434b0ecbc4202eebce11a98cfc5", + "shasum": "" + }, + "require": { + "php": ">=8.4", + "sebastian/object-reflector": "^6.0", + "sebastian/recursion-context": "^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^13.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "8.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "security": "https://github.com/sebastianbergmann/object-enumerator/security/policy", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/8.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/object-enumerator", + "type": "tidelift" + } + ], + "time": "2026-02-06T04:46:36+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "6.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "3ca042c2c60b0eab094f8a1b6a7093f4d4c72200" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/3ca042c2c60b0eab094f8a1b6a7093f4d4c72200", + "reference": "3ca042c2c60b0eab094f8a1b6a7093f4d4c72200", + "shasum": "" + }, + "require": { + "php": ">=8.4" + }, + "require-dev": { + "phpunit/phpunit": "^13.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "security": "https://github.com/sebastianbergmann/object-reflector/security/policy", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/6.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/object-reflector", + "type": "tidelift" + } + ], + "time": "2026-02-06T04:47:13+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "8.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "74c5af21f6a5833e91767ca068c4d3dfec15317e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/74c5af21f6a5833e91767ca068c4d3dfec15317e", + "reference": "74c5af21f6a5833e91767ca068c4d3dfec15317e", + "shasum": "" + }, + "require": { + "php": ">=8.4" + }, + "require-dev": { + "phpunit/phpunit": "^13.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "8.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "https://github.com/sebastianbergmann/recursion-context", + "support": { + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "security": "https://github.com/sebastianbergmann/recursion-context/security/policy", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/8.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/recursion-context", + "type": "tidelift" + } + ], + "time": "2026-02-06T04:51:28+00:00" + }, + { + "name": "sebastian/type", + "version": "7.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "42412224607bd3931241bbd17f38e0f972f5a916" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/42412224607bd3931241bbd17f38e0f972f5a916", + "reference": "42412224607bd3931241bbd17f38e0f972f5a916", + "shasum": "" + }, + "require": { + "php": ">=8.4" + }, + "require-dev": { + "phpunit/phpunit": "^13.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "support": { + "issues": "https://github.com/sebastianbergmann/type/issues", + "security": "https://github.com/sebastianbergmann/type/security/policy", + "source": "https://github.com/sebastianbergmann/type/tree/7.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/type", + "type": "tidelift" + } + ], + "time": "2026-02-06T04:52:09+00:00" + }, + { + "name": "sebastian/version", + "version": "7.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "ad37a5552c8e2b88572249fdc19b6da7792e021b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/ad37a5552c8e2b88572249fdc19b6da7792e021b", + "reference": "ad37a5552c8e2b88572249fdc19b6da7792e021b", + "shasum": "" + }, + "require": { + "php": ">=8.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "support": { + "issues": "https://github.com/sebastianbergmann/version/issues", + "security": "https://github.com/sebastianbergmann/version/security/policy", + "source": "https://github.com/sebastianbergmann/version/tree/7.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/version", + "type": "tidelift" + } + ], + "time": "2026-02-06T04:52:52+00:00" + }, + { + "name": "staabm/side-effects-detector", + "version": "1.0.5", + "source": { + "type": "git", + "url": "https://github.com/staabm/side-effects-detector.git", + "reference": "d8334211a140ce329c13726d4a715adbddd0a163" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/staabm/side-effects-detector/zipball/d8334211a140ce329c13726d4a715adbddd0a163", + "reference": "d8334211a140ce329c13726d4a715adbddd0a163", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^1.12.6", + "phpunit/phpunit": "^9.6.21", + "symfony/var-dumper": "^5.4.43", + "tomasvotruba/type-coverage": "1.0.0", + "tomasvotruba/unused-public": "1.0.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "lib/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A static analysis tool to detect side effects in PHP code", + "keywords": [ + "static analysis" + ], + "support": { + "issues": "https://github.com/staabm/side-effects-detector/issues", + "source": "https://github.com/staabm/side-effects-detector/tree/1.0.5" + }, + "funding": [ + { + "url": "https://github.com/staabm", + "type": "github" + } + ], + "time": "2024-10-20T05:08:20+00:00" + }, + { + "name": "symfony/browser-kit", + "version": "v8.0.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/browser-kit.git", + "reference": "0d998c101e1920fc68572209d1316fec0db728ef" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/browser-kit/zipball/0d998c101e1920fc68572209d1316fec0db728ef", + "reference": "0d998c101e1920fc68572209d1316fec0db728ef", + "shasum": "" + }, + "require": { + "php": ">=8.4", + "symfony/dom-crawler": "^7.4|^8.0" + }, + "require-dev": { + "symfony/css-selector": "^7.4|^8.0", + "symfony/http-client": "^7.4|^8.0", + "symfony/mime": "^7.4|^8.0", + "symfony/process": "^7.4|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\BrowserKit\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Simulates the behavior of a web browser, allowing you to make requests, click on links and submit forms programmatically", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/browser-kit/tree/v8.0.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-01-13T13:06:50+00:00" + }, + { + "name": "symfony/debug-bundle", + "version": "v8.0.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/debug-bundle.git", + "reference": "a837fd8ee1c60805479d8ead3474e3d13a10af2b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/debug-bundle/zipball/a837fd8ee1c60805479d8ead3474e3d13a10af2b", + "reference": "a837fd8ee1c60805479d8ead3474e3d13a10af2b", + "shasum": "" + }, + "require": { + "composer-runtime-api": ">=2.1", + "ext-xml": "*", + "php": ">=8.4", + "symfony/config": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/twig-bridge": "^7.4|^8.0", + "symfony/var-dumper": "^7.4|^8.0" + }, + "require-dev": { + "symfony/web-profiler-bundle": "^7.4|^8.0" + }, + "type": "symfony-bundle", + "autoload": { + "psr-4": { + "Symfony\\Bundle\\DebugBundle\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides a tight integration of the Symfony VarDumper component and the ServerLogCommand from MonologBridge into the Symfony full-stack framework", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/debug-bundle/tree/v8.0.7" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-03-03T07:49:33+00:00" + }, + { + "name": "symfony/dom-crawler", + "version": "v8.0.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/dom-crawler.git", + "reference": "7f504fe7fb7fa5fee40a653104842cf6f851a6d8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/7f504fe7fb7fa5fee40a653104842cf6f851a6d8", + "reference": "7f504fe7fb7fa5fee40a653104842cf6f851a6d8", + "shasum": "" + }, + "require": { + "php": ">=8.4", + "symfony/polyfill-ctype": "^1.8", + "symfony/polyfill-mbstring": "^1.0" + }, + "require-dev": { + "symfony/css-selector": "^7.4|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\DomCrawler\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases DOM navigation for HTML and XML documents", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/dom-crawler/tree/v8.0.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-02-17T13:07:04+00:00" + }, + { + "name": "symfony/maker-bundle", + "version": "v1.67.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/maker-bundle.git", + "reference": "6ce8b313845f16bcf385ee3cb31d8b24e30d5516" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/maker-bundle/zipball/6ce8b313845f16bcf385ee3cb31d8b24e30d5516", + "reference": "6ce8b313845f16bcf385ee3cb31d8b24e30d5516", + "shasum": "" + }, + "require": { + "composer-runtime-api": "^2.1", + "doctrine/inflector": "^2.0", + "nikic/php-parser": "^5.0", + "php": ">=8.1", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/console": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/deprecation-contracts": "^2.2|^3", + "symfony/filesystem": "^6.4|^7.0|^8.0", + "symfony/finder": "^6.4|^7.0|^8.0", + "symfony/framework-bundle": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/process": "^6.4|^7.0|^8.0" + }, + "conflict": { + "doctrine/doctrine-bundle": "<2.10", + "doctrine/orm": "<2.15" + }, + "require-dev": { + "composer/semver": "^3.0", + "doctrine/doctrine-bundle": "^2.10|^3.0", + "doctrine/orm": "^2.15|^3", + "doctrine/persistence": "^3.1|^4.0", + "symfony/http-client": "^6.4|^7.0|^8.0", + "symfony/phpunit-bridge": "^6.4.1|^7.0|^8.0", + "symfony/security-core": "^6.4|^7.0|^8.0", + "symfony/security-http": "^6.4|^7.0|^8.0", + "symfony/yaml": "^6.4|^7.0|^8.0", + "twig/twig": "^3.0|^4.x-dev" + }, + "type": "symfony-bundle", + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Bundle\\MakerBundle\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Maker helps you create empty commands, controllers, form classes, tests and more so you can forget about writing boilerplate code.", + "homepage": "https://symfony.com/doc/current/bundles/SymfonyMakerBundle/index.html", + "keywords": [ + "code generator", + "dev", + "generator", + "scaffold", + "scaffolding" + ], + "support": { + "issues": "https://github.com/symfony/maker-bundle/issues", + "source": "https://github.com/symfony/maker-bundle/tree/v1.67.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-03-18T13:39:06+00:00" + }, { "name": "symfony/web-profiler-bundle", "version": "v8.0.7", @@ -9474,81 +14543,6 @@ ], "time": "2026-03-04T08:20:53+00:00" }, - { - "name": "symfony/yaml", - "version": "v8.0.6", - "source": { - "type": "git", - "url": "https://github.com/symfony/yaml.git", - "reference": "5f006c50a981e1630bbb70ad409c5d85f9a716e0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/5f006c50a981e1630bbb70ad409c5d85f9a716e0", - "reference": "5f006c50a981e1630bbb70ad409c5d85f9a716e0", - "shasum": "" - }, - "require": { - "php": ">=8.4", - "symfony/polyfill-ctype": "^1.8" - }, - "conflict": { - "symfony/console": "<7.4" - }, - "require-dev": { - "symfony/console": "^7.4|^8.0" - }, - "bin": [ - "Resources/bin/yaml-lint" - ], - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Yaml\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Loads and dumps YAML files", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/yaml/tree/v8.0.6" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2026-02-09T10:14:57+00:00" - }, { "name": "theseer/tokenizer", "version": "2.0.1", @@ -9598,228 +14592,11 @@ } ], "time": "2025-12-08T11:19:18+00:00" - }, - { - "name": "twig/extra-bundle", - "version": "v3.24.0", - "source": { - "type": "git", - "url": "https://github.com/twigphp/twig-extra-bundle.git", - "reference": "6a621fcb1f28aa9ea7b34a99047ae0cdf5b834c9" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/twigphp/twig-extra-bundle/zipball/6a621fcb1f28aa9ea7b34a99047ae0cdf5b834c9", - "reference": "6a621fcb1f28aa9ea7b34a99047ae0cdf5b834c9", - "shasum": "" - }, - "require": { - "php": ">=8.1.0", - "symfony/framework-bundle": "^5.4|^6.4|^7.0|^8.0", - "symfony/twig-bundle": "^5.4|^6.4|^7.0|^8.0", - "twig/twig": "^3.2|^4.0" - }, - "require-dev": { - "league/commonmark": "^2.7", - "symfony/phpunit-bridge": "^6.4|^7.0", - "twig/cache-extra": "^3.0", - "twig/cssinliner-extra": "^3.0", - "twig/html-extra": "^3.0", - "twig/inky-extra": "^3.0", - "twig/intl-extra": "^3.0", - "twig/markdown-extra": "^3.0", - "twig/string-extra": "^3.0" - }, - "type": "symfony-bundle", - "autoload": { - "psr-4": { - "Twig\\Extra\\TwigExtraBundle\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com", - "homepage": "http://fabien.potencier.org", - "role": "Lead Developer" - } - ], - "description": "A Symfony bundle for extra Twig extensions", - "homepage": "https://twig.symfony.com", - "keywords": [ - "bundle", - "extra", - "twig" - ], - "support": { - "source": "https://github.com/twigphp/twig-extra-bundle/tree/v3.24.0" - }, - "funding": [ - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/twig/twig", - "type": "tidelift" - } - ], - "time": "2026-02-07T08:07:38+00:00" - }, - { - "name": "twig/twig", - "version": "v3.24.0", - "source": { - "type": "git", - "url": "https://github.com/twigphp/Twig.git", - "reference": "a6769aefb305efef849dc25c9fd1653358c148f0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/a6769aefb305efef849dc25c9fd1653358c148f0", - "reference": "a6769aefb305efef849dc25c9fd1653358c148f0", - "shasum": "" - }, - "require": { - "php": ">=8.1.0", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/polyfill-ctype": "^1.8", - "symfony/polyfill-mbstring": "^1.3" - }, - "require-dev": { - "php-cs-fixer/shim": "^3.0@stable", - "phpstan/phpstan": "^2.0@stable", - "psr/container": "^1.0|^2.0", - "symfony/phpunit-bridge": "^5.4.9|^6.4|^7.0" - }, - "type": "library", - "autoload": { - "files": [ - "src/Resources/core.php", - "src/Resources/debug.php", - "src/Resources/escaper.php", - "src/Resources/string_loader.php" - ], - "psr-4": { - "Twig\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com", - "homepage": "http://fabien.potencier.org", - "role": "Lead Developer" - }, - { - "name": "Twig Team", - "role": "Contributors" - }, - { - "name": "Armin Ronacher", - "email": "armin.ronacher@active-4.com", - "role": "Project Founder" - } - ], - "description": "Twig, the flexible, fast, and secure template language for PHP", - "homepage": "https://twig.symfony.com", - "keywords": [ - "templating" - ], - "support": { - "issues": "https://github.com/twigphp/Twig/issues", - "source": "https://github.com/twigphp/Twig/tree/v3.24.0" - }, - "funding": [ - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/twig/twig", - "type": "tidelift" - } - ], - "time": "2026-03-17T21:31:11+00:00" - }, - { - "name": "webmozart/assert", - "version": "2.1.6", - "source": { - "type": "git", - "url": "https://github.com/webmozarts/assert.git", - "reference": "ff31ad6efc62e66e518fbab1cde3453d389bcdc8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/webmozarts/assert/zipball/ff31ad6efc62e66e518fbab1cde3453d389bcdc8", - "reference": "ff31ad6efc62e66e518fbab1cde3453d389bcdc8", - "shasum": "" - }, - "require": { - "ext-ctype": "*", - "ext-date": "*", - "ext-filter": "*", - "php": "^8.2" - }, - "suggest": { - "ext-intl": "", - "ext-simplexml": "", - "ext-spl": "" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-feature/2-0": "2.0-dev" - } - }, - "autoload": { - "psr-4": { - "Webmozart\\Assert\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Bernhard Schussek", - "email": "bschussek@gmail.com" - }, - { - "name": "Woody Gilk", - "email": "woody.gilk@gmail.com" - } - ], - "description": "Assertions to validate method input/output with nice error messages.", - "keywords": [ - "assert", - "check", - "validate" - ], - "support": { - "issues": "https://github.com/webmozarts/assert/issues", - "source": "https://github.com/webmozarts/assert/tree/2.1.6" - }, - "time": "2026-02-27T10:28:38+00:00" } ], - "packages-dev": [], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": true, "prefer-lowest": false, "platform": { @@ -9827,6 +14604,6 @@ "ext-ctype": "*", "ext-iconv": "*" }, - "platform-dev": [], + "platform-dev": {}, "plugin-api-version": "2.6.0" } diff --git a/config/bundles.php b/config/bundles.php index 4e3a560..692ce90 100644 --- a/config/bundles.php +++ b/config/bundles.php @@ -7,10 +7,17 @@ return [ Symfony\Bundle\DebugBundle\DebugBundle::class => ['dev' => true], Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true], Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true], - Symfony\UX\StimulusBundle\StimulusBundle::class => ['all' => true], - Symfony\UX\Turbo\TurboBundle::class => ['all' => true], Twig\Extra\TwigExtraBundle\TwigExtraBundle::class => ['all' => true], Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true], Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true], Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true], + Endroid\QrCodeBundle\EndroidQrCodeBundle::class => ['all' => true], + Knp\Bundle\PaginatorBundle\KnpPaginatorBundle::class => ['all' => true], + KnpU\OAuth2ClientBundle\KnpUOAuth2ClientBundle::class => ['all' => true], + League\FlysystemBundle\FlysystemBundle::class => ['all' => true], + Liip\ImagineBundle\LiipImagineBundle::class => ['all' => true], + Nelmio\SecurityBundle\NelmioSecurityBundle::class => ['all' => true], + SpomkyLabs\PwaBundle\SpomkyLabsPwaBundle::class => ['all' => true], + Vich\UploaderBundle\VichUploaderBundle::class => ['all' => true], + Scheb\TwoFactorBundle\SchebTwoFactorBundle::class => ['all' => true], ]; diff --git a/config/cert/smime/certificate.pem b/config/cert/smime/certificate.pem new file mode 100644 index 0000000..9b19ee1 --- /dev/null +++ b/config/cert/smime/certificate.pem @@ -0,0 +1,125 @@ +Bag Attributes + localKeyID: 75 15 E3 C2 1D 7B 61 75 99 B9 22 D8 FD A4 19 AC 6B BE 1F 8F + friendlyName: contact@e-cosplay.fr +subject=CN = contact@e-cosplay.fr +issuer=C = IT, ST = Bergamo, L = Ponte San Pietro, O = Actalis S.p.A., CN = Actalis Client Authentication CA G3 +-----BEGIN CERTIFICATE----- +MIIF7TCCA9WgAwIBAgIQVGwKj9U0zzzn3LBvYN7iODANBgkqhkiG9w0BAQsFADCB +gTELMAkGA1UEBhMCSVQxEDAOBgNVBAgMB0JlcmdhbW8xGTAXBgNVBAcMEFBvbnRl +IFNhbiBQaWV0cm8xFzAVBgNVBAoMDkFjdGFsaXMgUy5wLkEuMSwwKgYDVQQDDCNB +Y3RhbGlzIENsaWVudCBBdXRoZW50aWNhdGlvbiBDQSBHMzAeFw0yNjAyMDEwOTM4 +NTdaFw0yNzAyMDEwOTM4NTdaMB8xHTAbBgNVBAMMFGNvbnRhY3RAZS1jb3NwbGF5 +LmZyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAutPj7aG1CkLIx760 +Ys4MCH0dOjpDqmKLJ9e8Lq2FAmaroRn2kS+Z3/qg3DhdpYnM0X1zrE8ZxCtCdLN/ +CRfc1l05CJrOMn8i/cSjggRD5DUntN8CHGikIzQdYrg6lup6VJv5HulZiAI+KooO +sMIlYqlsZM3BNvNuERQSNB+K7ZMQzYRFzpOnHFc8c/zMMQL5ih7iUBuy7W8rCCiG +XBmzY5ZtmJiHm1WFOOJKSNGTM37yrH8vXll91cmLm6S+EcLHPSPmdo7Lp/pxHOIX +h2pjbvDayh1kD/NNjWb0ZDEe7d/QD2JxnBDgXunmoA5J/Q0KWa5MX12FHLXgQOct +0VQcSQIDAQABo4IBwDCCAbwwDAYDVR0TAQH/BAIwADAfBgNVHSMEGDAWgBS+l6mq +hL+AvxBTfQky+eEuMhvPdzB+BggrBgEFBQcBAQRyMHAwOwYIKwYBBQUHMAKGL2h0 +dHA6Ly9jYWNlcnQuYWN0YWxpcy5pdC9jZXJ0cy9hY3RhbGlzLWF1dGNsaWczMDEG +CCsGAQUFBzABhiVodHRwOi8vb2NzcDA5LmFjdGFsaXMuaXQvVkEvQVVUSENMLUcz +MB8GA1UdEQQYMBaBFGNvbnRhY3RAZS1jb3NwbGF5LmZyMFIGA1UdIARLMEkwPAYG +K4EfEAEBMDIwMAYIKwYBBQUHAgEWJGh0dHBzOi8vd3d3LmFjdGFsaXMuaXQvYXJl +YS1kb3dubG9hZDAJBgdngQwBBQECMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEF +BQcDBDBIBgNVHR8EQTA/MD2gO6A5hjdodHRwOi8vY3JsMDkuYWN0YWxpcy5pdC9S +ZXBvc2l0b3J5L0FVVEhDTC1HMy9nZXRMYXN0Q1JMMB0GA1UdDgQWBBR1FePCHXth +dZm5Itj9pBmsa74fjzAOBgNVHQ8BAf8EBAMCBaAwDQYJKoZIhvcNAQELBQADggIB +AHU6pUBlyDdhCM4pya79GlvPDn64JL9WzWHZnKbMBFQBwmXCuvKZYK5c3aMhw7QV ++XIMPvEdzkhFkhBf4/t6ZGE3LZjn1G+pDWA+6rWe/yhhZKgtbR6ll02K0RUi9d5A +GukCOpSEaOMDE7Mn0inG+Rozx28PR/0qRVTCoBN7aCH7Z08BSigwIXsoXo6aglrv +9OI6IYRC4/x2oOA25g6ONl8I10mba17QLKY0OLaPYA23+wK6cd5XaQ+Xbl7anOAC +gWv3ZkGzLq/54jqhDfmu42HnwDD8PC+uXR/4JGFtQncAu9PYua8BW7STnAv2dY9E +j3p8BFzUH6LDijMRZxexZR97X+WHkA2Pei7eMEKmF5EpTRm7/q9LZLBiwa2aqfdf +5Cwjeq/pM96JHTj2wlA84e2EF+5yqslRKQ//lLtXLnch6oVi4hrhvCnFqkMnJt66 +Doxwm0U0X3o4TdHgPGhFwnxXWEstlOvhAK3fCkY2/ZKk9PUFnc4nsOr1c7ckgLKO +ks76UBmXPQnrsj66ahwiXaQDMPk14/ponWjPdpy6cvE0zIcA8G+zGJLXh/fRro9P +Bo+2YpoM9fA7W4vt3NwJyG+GdALEa0TNBNimppe1usdIFJ3yrdAaa8gJJ49qmGvE +0mxpxuXxY7nMiI5G+H4h1rx5r2VlyRJrpxBmC+zgtvMD +-----END CERTIFICATE----- +Bag Attributes + 2.16.840.1.113894.746875.1.1: + + friendlyName: Actalis Client Authentication CA G3 +subject=C = IT, ST = Bergamo, L = Ponte San Pietro, O = Actalis S.p.A., CN = Actalis Client Authentication CA G3 +issuer=C = IT, L = Milan, O = Actalis S.p.A./03358520967, CN = Actalis Authentication Root CA +-----BEGIN CERTIFICATE----- +MIIHbTCCBVWgAwIBAgIQFxA+3j2KHLXKBlGT58pDazANBgkqhkiG9w0BAQsFADBr +MQswCQYDVQQGEwJJVDEOMAwGA1UEBwwFTWlsYW4xIzAhBgNVBAoMGkFjdGFsaXMg +Uy5wLkEuLzAzMzU4NTIwOTY3MScwJQYDVQQDDB5BY3RhbGlzIEF1dGhlbnRpY2F0 +aW9uIFJvb3QgQ0EwHhcNMjAwNzA2MDg0NTQ3WhcNMzAwOTIyMTEyMjAyWjCBgTEL +MAkGA1UEBhMCSVQxEDAOBgNVBAgMB0JlcmdhbW8xGTAXBgNVBAcMEFBvbnRlIFNh +biBQaWV0cm8xFzAVBgNVBAoMDkFjdGFsaXMgUy5wLkEuMSwwKgYDVQQDDCNBY3Rh +bGlzIENsaWVudCBBdXRoZW50aWNhdGlvbiBDQSBHMzCCAiIwDQYJKoZIhvcNAQEB +BQADggIPADCCAgoCggIBAO3mh5ahwaS27cJCVfc/Dw8iYF8T4KZDiIZJkXkcGy8a +UA/cRgHu9ro6hsxRYe/ED4AIcSlarRh82HqtFSVQs4ZwikQW1V/icCIS91C2IVAG +a1YlKfedqgweqky+bBniUvRevVT0keZOqRTcO5hw007dL6FhYNmlZBt5IaJs1V6I +niRjokOHR++qWgrUGy5LefY6ACs9gZ8Bi0OMK9PZ37pibeQCsdmMRytl4Ej7JVWe +M/BtNIIprHwO1LY0/8InpGOmdG+5LC6xHLzg53B0HvVUqzUQNePUhNwJZFmmTP46 +FXovxmH4/SuY5IkXop0eJqjN+dxRHHizngYUk1EaTHUOcLFy4vQ0kxgbjb+GsNg6 +M2/6gZZIRk78JPdpotIwHnBNtkp9wPVH61NqdcP7kbPkyLXkNMTtAfydpmNnGqqH +LEvUrK4iBpUPG9C09KOjm9OyhrT2uf5SLzJsee9g79r/rw4hAgcsZtR3YI6fCbRO +JncmD+hgbHCck+9TWcNc1x5xZMgm8UXmoPamkkfceAlVV49QQ5jUTgqneTQHyF1F +2ExXmf47pEIoJMVxloRIXywQuB2uqcIs8/X6tfsMDynFmhfT/0mTrgQ6xt9DIsgm +WuuhvZhLReWS7oeKxnyqscuGeTMXnLs7fjGZq0inyhnlznhA/4rl+WdNjNaO4jEv +AgMBAAGjggH0MIIB8DAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFFLYiDrI +n3hm7YnzezhwlMkCAjbQMEEGCCsGAQUFBwEBBDUwMzAxBggrBgEFBQcwAYYlaHR0 +cDovL29jc3AwNS5hY3RhbGlzLml0L1ZBL0FVVEgtUk9PVDBFBgNVHSAEPjA8MDoG +BFUdIAAwMjAwBggrBgEFBQcCARYkaHR0cHM6Ly93d3cuYWN0YWxpcy5pdC9hcmVh +LWRvd25sb2FkMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDBDCB4wYDVR0f +BIHbMIHYMIGWoIGToIGQhoGNbGRhcDovL2xkYXAwNS5hY3RhbGlzLml0L2NuJTNk +QWN0YWxpcyUyMEF1dGhlbnRpY2F0aW9uJTIwUm9vdCUyMENBLG8lM2RBY3RhbGlz +JTIwUy5wLkEuJTJmMDMzNTg1MjA5NjcsYyUzZElUP2NlcnRpZmljYXRlUmV2b2Nh +dGlvbkxpc3Q7YmluYXJ5MD2gO6A5hjdodHRwOi8vY3JsMDUuYWN0YWxpcy5pdC9S +ZXBvc2l0b3J5L0FVVEgtUk9PVC9nZXRMYXN0Q1JMMB0GA1UdDgQWBBS+l6mqhL+A +vxBTfQky+eEuMhvPdzAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQELBQADggIB +ACab5xtZDXSzEgPp51X3hICFzULDO2EcV8em5hLfSCKxZR9amCnjcODVfMbaKfdU +ZXtevMIIZmHgkz9dBan7ijGbJXjZCPP29zwZGSyCjpfadg5s9hnNCN1r3DGwIHfy +LgbcfffDyV/2wW+XTGbhldnazZsX892q+srRmC8XnX4ygg+eWL/AkHDenvbFuTlJ +vUyd5I7e1nb3dYXMObPu24ZTQ9/K1hSQbs7pqecaptTUjoIDpBUpSp4Us+h1I4MA +WonemKYoPS9f0y65JrRCKcfsKSI+1kwPSanDDMiydKzeo46XrS0hlA5NzQjqUJ7U +suGvPtDvknqc0v03nNXBnUjejYtvwO3sEDXdUW5m9kjNqlQZXzdHumZJVqPUGKTW +cn9Hf3d7qbCmmxPXjQoNUuHg56fLCanZWkEO4SP1GAgIA7SyJu/yffv0ts7sBFrS +TD3L2mCAXM3Y8BfblvvDSf2bvySm/fPe9brmuzrCXsTxUQc1+/z5ydvzV3E3cLnU +oSXP6XfXNyEVO6sPkcUSnISHM798xLkCTB5EkjPCjPE2zs4v9L9JVOkkskvW6RnW +WccdfR3fELNHL/kep8re6IbbYs8Hn5GM0Ohs8CMDPYEox+QX/6/SnOfyaqqSilBo +nMQBstsymBBgdEKO+tTHHCMnJQVvZn7jRQ20wXgxMrvN +-----END CERTIFICATE----- +Bag Attributes + 2.16.840.1.113894.746875.1.1: + friendlyName: Actalis Authentication Root CA +subject=C = IT, L = Milan, O = Actalis S.p.A./03358520967, CN = Actalis Authentication Root CA +issuer=C = IT, L = Milan, O = Actalis S.p.A./03358520967, CN = Actalis Authentication Root CA +-----BEGIN CERTIFICATE----- +MIIFuzCCA6OgAwIBAgIIVwoRl0LE48wwDQYJKoZIhvcNAQELBQAwazELMAkGA1UE +BhMCSVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8w +MzM1ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290 +IENBMB4XDTExMDkyMjExMjIwMloXDTMwMDkyMjExMjIwMlowazELMAkGA1UEBhMC +SVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8wMzM1 +ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290IENB +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAp8bEpSmkLO/lGMWwUKNv +UTufClrJwkg4CsIcoBh/kbWHuUA/3R1oHwiD1S0eiKD4j1aPbZkCkpAW1V8IbInX +4ay8IMKx4INRimlNAJZaby/ARH6jDuSRzVju3PvHHkVH3Se5CAGfpiEd9UEtL0z9 +KK3giq0itFZljoZUj5NDKd45RnijMCO6zfB9E1fAXdKDa0hMxKufgFpbOr3JpyI/ +gCczWw63igxdBzcIy2zSekciRDXFzMwujt0q7bd9Zg1fYVEiVRvjRuPjPdA1Yprb +rxTIW6HMiRvhMCb8oJsfgadHHwTrozmSBp+Z07/T6k9QnBn+locePGX2oxgkg4YQ +51Q+qDp2JE+BIcXjDwL4k5RHILv+1A7TaLndxHqEguNTVHnd25zS8gebLra8Pu2F +be8lEfKXGkJh90qX6IuxEAf6ZYGyojnP9zz/GPvG8VqLWeICrHuS0E4UT1lF9gxe +KF+w6D9Fz8+vm2/7hNN3WpVvrJSEnu68wEqPSpP4RCHiMUVhUE4Q2OM1fEwZtN4F +v6MGn8i1zeQf1xcGDXqVdFUNaBr8EBtiZJ1t4JWgw5QHVw0U5r0F+7if5t+L4sbn +fpb2U8WANFAoWPASUHEXMLrmeGO89LKtmyuy/uE5jF66CyCU3nuDuP/jVo23Eek7 +jPKxwV2dpAtMK9myGPW1n0sCAwEAAaNjMGEwHQYDVR0OBBYEFFLYiDrIn3hm7Ynz +ezhwlMkCAjbQMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUUtiIOsifeGbt +ifN7OHCUyQICNtAwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQAL +e3KHwGCmSUyIWOYdiPcUZEim2FgKDk8TNd81HdTtBjHIgT5q1d07GjLukD0R0i70 +jsNjLiNmsGe+b7bAEzlgqqI0JZN1Ut6nna0Oh4lScWoWPBkdg/iaKWW+9D+a2fDz +WochcYBNy+A4mz+7+uAwTc+G02UQGRjRlwKxK3JCaKygvU5a2hi/a5iB0P2avl4V +SM0RFbnAKVy06Ij3Pjaut2L9HmLecHgQHEhb2rykOLpn7VU+Xlff1ANATIGk0k9j +pwlCCRT8AKnCgHNPLsBA2RF7SOp6AsDT6ygBJlh0wcBzIm2Tlf05fbsq4/aC4yyX +X04fkZT6/iyj2HYauE2yOE+b+h1IYHkm4vP9qdCa6HCPSXrW5b0KDtst842/6+Ok +fcvHlXHo2qN8xcL4dJIEG4aspCJTQLas/kx2z/uUMsA1n3Y/buWQbqCmJqK4LL7R +K4X9p2jIugErsWx0Hbhzlefut8cl8ABMALJ+tguLHPPAUJ4lueAI3jZm/zel0btU +ZCzJJ7VLkn5l/9Mt4blOvH+kQSGQQXemOR/qnuOf0GZvBeyqdn6/axag67XH/JJU +LysRJyU3eExRarDzzFhdFPFqSBX/wge2sY0PjlxQRrM9vwGYT7JZVEc+NHt4bVaT +LnPqZih4zR0Uv6CPLy64Lo7yFIrM6bV8+2ydDKXhlg== +-----END CERTIFICATE----- diff --git a/config/cert/smime/email_cer.p12 b/config/cert/smime/email_cer.p12 new file mode 100644 index 0000000..ba45424 Binary files /dev/null and b/config/cert/smime/email_cer.p12 differ diff --git a/config/cert/smime/private-key.pem b/config/cert/smime/private-key.pem new file mode 100644 index 0000000..a644aee --- /dev/null +++ b/config/cert/smime/private-key.pem @@ -0,0 +1,32 @@ +Bag Attributes + localKeyID: 75 15 E3 C2 1D 7B 61 75 99 B9 22 D8 FD A4 19 AC 6B BE 1F 8F + friendlyName: contact@e-cosplay.fr +Key Attributes: +-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC60+PtobUKQsjH +vrRizgwIfR06OkOqYosn17wurYUCZquhGfaRL5nf+qDcOF2liczRfXOsTxnEK0J0 +s38JF9zWXTkIms4yfyL9xKOCBEPkNSe03wIcaKQjNB1iuDqW6npUm/ke6VmIAj4q +ig6wwiViqWxkzcE2824RFBI0H4rtkxDNhEXOk6ccVzxz/MwxAvmKHuJQG7LtbysI +KIZcGbNjlm2YmIebVYU44kpI0ZMzfvKsfy9eWX3VyYubpL4Rwsc9I+Z2jsun+nEc +4heHamNu8NrKHWQP802NZvRkMR7t39APYnGcEOBe6eagDkn9DQpZrkxfXYUcteBA +5y3RVBxJAgMBAAECggEASCuXNlJL0blnv0WLgEmRO2w9cKNWRUkI8Wy0rKFqKrnH +nhWd+orVWcEDB98cSdi4Dsrz9sGdtfpyEwvNzJDzxblAUXMfqUEUnswpz41fPpZM +DuJA+gFwOoskuOBq/aZ5eBrCeu7wn4oiQqOPNjfhYkIJdc1H0Lbrs1w6BfmTXN9S +ilSL4GMv7YpEBPt86wiReIbE0DIqaR9Vm1UupkJe7WMc9LrxW6OM4kD5xQTFdJLC +n6QbbX+TE3q+d6TrckK8PeBm+gIX2+NdEuuz7gpZGSybfqN1wqbkziUsifi+7gQi +pxwxhDmCQ08DHplcBdjg1ZldVhaAdHEqCyWZtIZQGwKBgQD9RNXOskPhGBP28oZz +a7APhROKHcEtb9BRj3mwzwIMr3SzJjoBI4SfRR3QcU8jD2uCLNio/cSqjAJVyk2f +Z9wDONnXy4aNrsze0Fx+0t1VqIOVocFDJJHndHCiq2HHorqArvHtZxkj/0xIUx+K +nVltevm7qLSAtR/vWTM1c9Zp/wKBgQC816PZxNEpyg5mgO+3gn5yONsObh2ZyZ/v +KPEmFBjl69AfAxKeaEZaj35RmzdnAFKlv9QzNdAM67+oOIThcaQ+diyNUQegaJwc +amUZHWjQ7RAJNM7lQz85zn9s/MZVC8iNF7xlPWK8Y/ifMwk5e+pAQ79PVwxKiW8y +doXv1uuptwKBgQCPfVJNGr5OZx/YhysZdWd4Q4MAez8ZCQJTUyA0xfN5UGajoQK3 +5nGa8sOjdq7JRFE6nUHa7HthzJT+GfHEYElMIgd5tTt0RhaYHiBpUfpIL4LtYYln +Cu8G3Pd7kfGB83YKthMm14Rs2pVTXJTAKcmit0uJwFkTmoqPRlA29nwB7QKBgQCp +iVtzxcwWnW1iPz13XfNRbpPTl66Bg89PMk2VZxOXT6cQQHz1tB39bbf1YclScqrA +XcEPnK9E+l2dk+hQUesnnIaQ9H9JVsFYjXhoHMweTcZIzZ01nLLlnesumJ0ieyq4 +6OddaMf85C+wFWyhrAr5WRknDLpfLJWlE11ZnnUCbwKBgQCXOLstj7bEi8N3H8PV +UQlkFKrAmmEE4i3Y3Qqa1DMbm2XJBzPQQJIrDlCeIWs0jk0upOY34W+deWH5gbvW +a/6bpvSzbWqgFD5DnayVkb7CvgEg+CKdUoUumRSs38pet29c/pdKPUxhMq8voIEg +HpnyJcapAiLw0hv4ql380onf0A== +-----END PRIVATE KEY----- diff --git a/config/packages/dev/nelmio_security.yaml b/config/packages/dev/nelmio_security.yaml new file mode 100644 index 0000000..5c21b3f --- /dev/null +++ b/config/packages/dev/nelmio_security.yaml @@ -0,0 +1,10 @@ +nelmio_security: + csp: + enforce: + script-src: + - 'wasm-unsafe-eval' + - 'http://localhost:5173/' + style-src: + - 'unsafe-inline' + connect-src: + - 'ws://localhost:5173' diff --git a/config/packages/flysystem.yaml b/config/packages/flysystem.yaml new file mode 100644 index 0000000..d104e2e --- /dev/null +++ b/config/packages/flysystem.yaml @@ -0,0 +1,7 @@ +# Read the documentation at https://github.com/thephpleague/flysystem-bundle/blob/master/docs/1-getting-started.md +flysystem: + storages: + default.storage: + adapter: 'local' + options: + directory: '%kernel.project_dir%/var/storage/default' diff --git a/config/packages/framework.yaml b/config/packages/framework.yaml index 7e1ee1f..141da38 100644 --- a/config/packages/framework.yaml +++ b/config/packages/framework.yaml @@ -1,6 +1,8 @@ # see https://symfony.com/doc/current/reference/configuration/framework.html framework: secret: '%env(APP_SECRET)%' + trusted_proxies: 'REMOTE_ADDR' + trusted_headers: ['x-forwarded-for', 'x-forwarded-host', 'x-forwarded-proto', 'x-forwarded-port'] # Note that the session will be started ONLY if you read or write from it. session: true diff --git a/config/packages/http_discovery.yaml b/config/packages/http_discovery.yaml new file mode 100644 index 0000000..2a789e7 --- /dev/null +++ b/config/packages/http_discovery.yaml @@ -0,0 +1,10 @@ +services: + Psr\Http\Message\RequestFactoryInterface: '@http_discovery.psr17_factory' + Psr\Http\Message\ResponseFactoryInterface: '@http_discovery.psr17_factory' + Psr\Http\Message\ServerRequestFactoryInterface: '@http_discovery.psr17_factory' + Psr\Http\Message\StreamFactoryInterface: '@http_discovery.psr17_factory' + Psr\Http\Message\UploadedFileFactoryInterface: '@http_discovery.psr17_factory' + Psr\Http\Message\UriFactoryInterface: '@http_discovery.psr17_factory' + + http_discovery.psr17_factory: + class: Http\Discovery\Psr17Factory diff --git a/config/packages/knpu_oauth2_client.yaml b/config/packages/knpu_oauth2_client.yaml new file mode 100644 index 0000000..5849335 --- /dev/null +++ b/config/packages/knpu_oauth2_client.yaml @@ -0,0 +1,9 @@ +knpu_oauth2_client: + clients: + keycloak: + type: keycloak + auth_server_url: '%env(OAUTH_KEYCLOAK_URL)%' + realm: '%env(OAUTH_KEYCLOAK_REALM)%' + client_id: '%env(OAUTH_KEYCLOAK_CLIENT_ID)%' + client_secret: '%env(OAUTH_KEYCLOAK_CLIENT_SECRET)%' + redirect_route: connect_keycloak_check diff --git a/config/packages/liip_imagine.yaml b/config/packages/liip_imagine.yaml new file mode 100644 index 0000000..3a6a147 --- /dev/null +++ b/config/packages/liip_imagine.yaml @@ -0,0 +1,49 @@ +liip_imagine: + driver: "imagick" + twig: + mode: lazy + webp: + generate: true + quality: 85 + filter_sets: + webp: + quality: 85 + format: 'webp' + filters: + strip: ~ + avatar_partner: + quality: 75 + format: 'webp' + filters: + strip: ~ + thumbnail: { size: [ 128, 128 ], mode: inset } + members: + quality: 75 + format: 'webp' + filters: + strip: ~ + thumbnail: { size: [ 128, 128 ], mode: inset } + navbar_logo: + quality: 85 + format: 'webp' + filters: + strip: ~ + thumbnail: { size: [ 48, 48 ], mode: inset } + avatar: + quality: 80 + format: 'webp' + filters: + strip: ~ + downscale: { max: [ 128, 128 ] } + avatar_small: + quality: 80 + format: 'webp' + filters: + strip: ~ + downscale: { max: [ 40, 40 ] } + logo: + quality: 85 + format: 'webp' + filters: + strip: ~ + thumbnail: { size: [ 99, 56 ], mode: inset } diff --git a/config/packages/nelmio_security.yaml b/config/packages/nelmio_security.yaml new file mode 100644 index 0000000..f156116 --- /dev/null +++ b/config/packages/nelmio_security.yaml @@ -0,0 +1,97 @@ +nelmio_security: + + clickjacking: + paths: + '^/.*': DENY + + content_type: + nosniff: true + + referrer_policy: + enabled: true + policies: + - 'no-referrer' + - 'strict-origin-when-cross-origin' + + csp: + enforce: + level1_fallback: false + browser_adaptive: + enabled: false + report-uri: '%router.request_context.base_url%/my-csp-report' + frame-ancestors: + - 'self' + frame-src: + - 'self' + - 'https://stripe.com' + - 'https://*.stripe.com' + - 'https://js.stripe.com' + - 'https://cloudflare.com' + - 'https://*.cloudflareinsights.com' + - 'https://challenges.cloudflare.com' + script-src: + - 'self' + - 'https://static.cloudflareinsights.com' + - 'https://challenges.cloudflare.com' + - 'https://cdn.jsdelivr.net' + - 'https://js.stripe.com' + - 'unsafe-inline' + style-src: + - 'self' + - 'https://fonts.googleapis.com' + - 'https://cdnjs.cloudflare.com' + - 'https://cdn.jsdelivr.net' + - 'unsafe-inline' + img-src: + - 'self' + - 'data:' + - 'https://*.tile.openstreetmap.org' + - 'https://*.basemaps.cartocdn.com' + - 'https://cdn.jsdelivr.net' + worker-src: + - 'self' + - 'blob:' + connect-src: + - 'self' + - 'https://cloudflareinsights.com' + - 'https://static.cloudflareinsights.com' + - 'https://challenges.cloudflare.com' + - 'https://nominatim.openstreetmap.org' + - 'https://cdn.jsdelivr.net' + - 'https://api.stripe.com' + font-src: + - 'self' + - 'https://cdnjs.cloudflare.com' + - 'https://fonts.googleapis.com' + - 'https://fonts.gstatic.com' + object-src: + - 'none' + form-action: + - 'self' + - 'https://auth.esy-web.dev' + - 'https://*.stripe.com' + - 'https://checkout.stripe.com' + block-all-mixed-content: true + + permissions_policy: + enabled: true + policies: + payment: ['self'] + camera: ['self'] + microphone: [] + geolocation: ['self'] + + external_redirects: + override: /external-redirect + forward_as: redirUrl + log: true + allow_list: + - cloudflareinsights.com + - static.cloudflareinsights.com + - stripe.com + - connect.stripe.com + - checkout.stripe.com + - hooks.stripe.com + - dashboard.stripe.com + - auth.esy-web.dev + - challenges.cloudflare.com diff --git a/config/packages/packages/asset_mapper.yaml b/config/packages/packages/asset_mapper.yaml new file mode 100644 index 0000000..f7653e9 --- /dev/null +++ b/config/packages/packages/asset_mapper.yaml @@ -0,0 +1,11 @@ +framework: + asset_mapper: + # The paths to make available to the asset mapper. + paths: + - assets/ + missing_import_mode: strict + +when@prod: + framework: + asset_mapper: + missing_import_mode: warn diff --git a/config/packages/packages/cache.yaml b/config/packages/packages/cache.yaml new file mode 100644 index 0000000..024348e --- /dev/null +++ b/config/packages/packages/cache.yaml @@ -0,0 +1,19 @@ +framework: + cache: + # Nom unique de votre application : utilisé pour calculer des espaces de noms stables pour les clés de cache. + # Ceci est CRUCIAL pour éviter les collisions de clés si plusieurs applications partagent le même serveur de cache (ex: Redis). + # Décommentez et remplacez par une valeur unique à votre projet (ex: "mon_entreprise/mon_app") + prefix_seed: 'e-cosplay/contest' # <-- REMPLACEZ CECI PAR UN NOM UNIQUE À VOTRE PROJET + + # En production, utilisez un adaptateur de cache rapide et performant comme Redis. + # Assurez-vous que votre serveur Redis est accessible. + app: cache.adapter.redis + default_redis_provider: '%env(REDIS_DSN)%' + # Vous pouvez également optimiser les pools personnalisés pour la production si besoin. + pools: + my.user_data_cache: + adapter: cache.adapter.redis + my.api_data_cache: + adapter: cache.adapter.redis + vite_cache_pool: + adapter: cache.adapter.redis diff --git a/config/packages/packages/csrf.yaml b/config/packages/packages/csrf.yaml new file mode 100644 index 0000000..40d4040 --- /dev/null +++ b/config/packages/packages/csrf.yaml @@ -0,0 +1,11 @@ +# Enable stateless CSRF protection for forms and logins/logouts +framework: + form: + csrf_protection: + token_id: submit + + csrf_protection: + stateless_token_ids: + - submit + - authenticate + - logout diff --git a/config/packages/packages/debug.yaml b/config/packages/packages/debug.yaml new file mode 100644 index 0000000..ad874af --- /dev/null +++ b/config/packages/packages/debug.yaml @@ -0,0 +1,5 @@ +when@dev: + debug: + # Forwards VarDumper Data clones to a centralized server allowing to inspect dumps on CLI or in your browser. + # See the "server:dump" command to start a new server. + dump_destination: "tcp://%env(VAR_DUMPER_SERVER)%" diff --git a/config/packages/packages/dev/nelmio_security.yaml b/config/packages/packages/dev/nelmio_security.yaml new file mode 100644 index 0000000..f028c4d --- /dev/null +++ b/config/packages/packages/dev/nelmio_security.yaml @@ -0,0 +1,8 @@ +nelmio_security: + csp: + enforce: + script-src: + - "http://localhost:5173" + connect-src: + - "ws://localhost:5173/" # Autorise le WebSocket de Vite + diff --git a/config/packages/packages/dev/pwa.yaml b/config/packages/packages/dev/pwa.yaml new file mode 100644 index 0000000..9bc74ae --- /dev/null +++ b/config/packages/packages/dev/pwa.yaml @@ -0,0 +1,3 @@ +pwa: + serviceworker: + enabled: false diff --git a/config/packages/packages/doctrine.yaml b/config/packages/packages/doctrine.yaml new file mode 100644 index 0000000..25138b9 --- /dev/null +++ b/config/packages/packages/doctrine.yaml @@ -0,0 +1,54 @@ +doctrine: + dbal: + url: '%env(resolve:DATABASE_URL)%' + + # IMPORTANT: You MUST configure your server version, + # either here or in the DATABASE_URL env var (see .env file) + #server_version: '16' + + profiling_collect_backtrace: '%kernel.debug%' + use_savepoints: true + orm: + auto_generate_proxy_classes: true + enable_lazy_ghost_objects: true + report_fields_where_declared: true + validate_xml_mapping: true + naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware + identity_generation_preferences: + Doctrine\DBAL\Platforms\PostgreSQLPlatform: identity + auto_mapping: true + mappings: + App: + type: attribute + is_bundle: false + dir: '%kernel.project_dir%/src/Entity' + prefix: 'App\Entity' + alias: App + controller_resolver: + auto_mapping: false + +when@test: + doctrine: + dbal: + # "TEST_TOKEN" is typically set by ParaTest + dbname_suffix: '_test%env(default::TEST_TOKEN)%' + +when@prod: + doctrine: + orm: + auto_generate_proxy_classes: false + proxy_dir: '%kernel.build_dir%/doctrine/orm/Proxies' + query_cache_driver: + type: pool + pool: doctrine.system_cache_pool + result_cache_driver: + type: pool + pool: doctrine.result_cache_pool + + framework: + cache: + pools: + doctrine.result_cache_pool: + adapter: cache.app + doctrine.system_cache_pool: + adapter: cache.system diff --git a/config/packages/packages/doctrine_migrations.yaml b/config/packages/packages/doctrine_migrations.yaml new file mode 100644 index 0000000..29231d9 --- /dev/null +++ b/config/packages/packages/doctrine_migrations.yaml @@ -0,0 +1,6 @@ +doctrine_migrations: + migrations_paths: + # namespace is arbitrary but should be different from App\Migrations + # as migrations classes should NOT be autoloaded + 'DoctrineMigrations': '%kernel.project_dir%/migrations' + enable_profiler: false diff --git a/config/packages/packages/flysystem.yaml b/config/packages/packages/flysystem.yaml new file mode 100644 index 0000000..d104e2e --- /dev/null +++ b/config/packages/packages/flysystem.yaml @@ -0,0 +1,7 @@ +# Read the documentation at https://github.com/thephpleague/flysystem-bundle/blob/master/docs/1-getting-started.md +flysystem: + storages: + default.storage: + adapter: 'local' + options: + directory: '%kernel.project_dir%/var/storage/default' diff --git a/config/packages/packages/framework.yaml b/config/packages/packages/framework.yaml new file mode 100644 index 0000000..dbb8776 --- /dev/null +++ b/config/packages/packages/framework.yaml @@ -0,0 +1,24 @@ +# see https://symfony.com/doc/current/reference/configuration/framework.html +framework: + secret: '%env(APP_SECRET)%' + + http_cache: + enabled: true + default_ttl: 3600 + stale_while_revalidate: 3600 + stale_if_error: 3600 + session: + name: ecosplay_session + cookie_lifetime: 3600 + cookie_secure: true + + #esi: true + #fragments: true + trusted_proxies: '103.21.244.0/22,103.22.200.0/22,103.31.4.0/22,104.16.0.0/13,104.24.0.0/14,108.162.192.0/18,131.0.72.0/22,141.101.64.0/18,162.158.0.0/15,172.64.0.0/13,173.245.48.0/20,188.114.96.0/20,190.93.240.0/20,197.234.240.0/22,198.41.128.0/17' + trusted_headers: [ 'x-forwarded-for', 'x-forwarded-host', 'x-forwarded-proto', 'x-forwarded-port', 'x-forwarded-prefix' ] + +when@test: + framework: + test: true + session: + storage_factory_id: session.storage.factory.mock_file diff --git a/config/packages/packages/google_apiclient.yaml b/config/packages/packages/google_apiclient.yaml new file mode 100644 index 0000000..70b4b80 --- /dev/null +++ b/config/packages/packages/google_apiclient.yaml @@ -0,0 +1,11 @@ +services: + Google\Client: + class: Google\Client + calls: + # Authentication with "API key" + - [setDeveloperKey, ['%env(GOOGLE_API_KEY)%']] + # Authentication with "OAuth 2.0" using Client ID & Secret + - [setClientId, ['%env(GOOGLE_CLIENT_ID)%']] + - [setClientSecret, ['%env(GOOGLE_CLIENT_SECRET)%']] + # Authentication with "OAuth 2.0" or "Service account" using JSON + - [setAuthConfig, ['%env(resolve:GOOGLE_AUTH_CONFIG)%']] diff --git a/config/packages/packages/knpu_oauth2_client.yaml b/config/packages/packages/knpu_oauth2_client.yaml new file mode 100644 index 0000000..5849335 --- /dev/null +++ b/config/packages/packages/knpu_oauth2_client.yaml @@ -0,0 +1,9 @@ +knpu_oauth2_client: + clients: + keycloak: + type: keycloak + auth_server_url: '%env(OAUTH_KEYCLOAK_URL)%' + realm: '%env(OAUTH_KEYCLOAK_REALM)%' + client_id: '%env(OAUTH_KEYCLOAK_CLIENT_ID)%' + client_secret: '%env(OAUTH_KEYCLOAK_CLIENT_SECRET)%' + redirect_route: connect_keycloak_check diff --git a/config/packages/packages/liip_imagine.yaml b/config/packages/packages/liip_imagine.yaml new file mode 100644 index 0000000..81c29fa --- /dev/null +++ b/config/packages/packages/liip_imagine.yaml @@ -0,0 +1,28 @@ +liip_imagine: + driver: "gd" + twig: + mode: lazy + filter_sets: + webp: + quality: 85 + format: 'webp' + filters: + strip: ~ + avatar_partner: + quality: 75 + format: 'webp' + filters: + strip: ~ + thumbnail: { size: [ 128, 128 ], mode: inset } + members: + quality: 75 + format: 'webp' + filters: + strip: ~ + thumbnail: { size: [ 128, 128 ], mode: inset } + logo: + quality: 85 + format: 'webp' + filters: + strip: ~ + thumbnail: { size: [ 99, 56 ], mode: inset } diff --git a/config/packages/packages/mailer.yaml b/config/packages/packages/mailer.yaml new file mode 100644 index 0000000..56a650d --- /dev/null +++ b/config/packages/packages/mailer.yaml @@ -0,0 +1,3 @@ +framework: + mailer: + dsn: '%env(MAILER_DSN)%' diff --git a/config/packages/packages/messenger.yaml b/config/packages/packages/messenger.yaml new file mode 100644 index 0000000..a6ea6fe --- /dev/null +++ b/config/packages/packages/messenger.yaml @@ -0,0 +1,8 @@ +# config/packages/messenger.yaml +framework: + messenger: + transports: + async: "%env(MESSENGER_TRANSPORT_DSN)%" + + routing: + diff --git a/config/packages/packages/monolog.yaml b/config/packages/packages/monolog.yaml new file mode 100644 index 0000000..465b5b4 --- /dev/null +++ b/config/packages/packages/monolog.yaml @@ -0,0 +1,51 @@ +monolog: + channels: + - deprecation # Deprecations are logged in the dedicated "deprecation" channel when it exists + +when@dev: + monolog: + handlers: + main: + type: stream + path: "%kernel.logs_dir%/%kernel.environment%.log" + level: debug + channels: ["!event"] + # uncomment to get logging in your browser + # you may have to allow bigger header sizes in your Web server configuration + #firephp: + # type: firephp + # level: info + #chromephp: + # type: chromephp + # level: info + console: + type: console + process_psr_3_messages: false + channels: ["!event", "!doctrine", "!console"] + +when@test: + monolog: + handlers: + main: + type: fingers_crossed + action_level: error + handler: nested + excluded_http_codes: [404, 405] + channels: ["!event"] + nested: + type: stream + path: "%kernel.logs_dir%/%kernel.environment%.log" + level: debug + +when@prod: + monolog: + handlers: + main: + type: stream + path: "%kernel.logs_dir%/%kernel.environment%.log" + level: debug + channels: ["!event"] + console: + type: console + process_psr_3_messages: false + channels: ["!event", "!doctrine", "!console"] diff --git a/config/packages/packages/nelmio_cors.yaml b/config/packages/packages/nelmio_cors.yaml new file mode 100644 index 0000000..c766508 --- /dev/null +++ b/config/packages/packages/nelmio_cors.yaml @@ -0,0 +1,10 @@ +nelmio_cors: + defaults: + origin_regex: true + allow_origin: ['%env(CORS_ALLOW_ORIGIN)%'] + allow_methods: ['GET', 'OPTIONS', 'POST', 'PUT', 'PATCH', 'DELETE'] + allow_headers: ['Content-Type', 'Authorization'] + expose_headers: ['Link'] + max_age: 3600 + paths: + '^/': null diff --git a/config/packages/packages/nelmio_security.yaml b/config/packages/packages/nelmio_security.yaml new file mode 100644 index 0000000..7e900a8 --- /dev/null +++ b/config/packages/packages/nelmio_security.yaml @@ -0,0 +1,62 @@ +nelmio_security: + # Content Security Policy (CSP) + referrer_policy: + enabled: true + policies: + - 'strict-origin-when-cross-origin' + permissions_policy: + enabled: true + policies: + camera: [self] # Correct : sans les guillemets simples internes + microphone: [self] # Correct + geolocation: [self] # Correct + fullscreen: [self] # Correct + payment: [self] # Correct + # Si tu veux bloquer une fonction pour tout le monde : + usb: [] + csp: + hash: + algorithm: 'sha256' + enforce: + default-src: ["'self'"] + worker-src: ["'self'"] + script-src: + - "'self'" + - "nonce" + - "https://sentry.esy-web.dev" + - "https://chat.esy-web.dev" + - "https://static.cloudflareinsights.com" + - "https://challenges.cloudflare.com" + connect-src: + - "'self'" + - "https://sentry.esy-web.dev" + - "https://chat.esy-web.dev" + - "https://auth.esy-web.dev" + - "https://cloudflareinsights.com" + - "https://challenges.cloudflare.com" + - "https://tools-security.esy-web.dev" + - "https://checkout.stripe.com/" + - "https://cdnjs.cloudflare.com" + frame-src: + - "'self'" + - "https://chat.esy-web.dev" + - "https://challenges.cloudflare.com" + - "https://climate.stripe.com/" + style-src: + - "'self'" + - "'unsafe-inline'" + - "https://fonts.googleapis.com" + - "https://chat.esy-web.dev" + - "https://cdnjs.cloudflare.com" + img-src: + - "'self'" + - "data:" + - "https://chat.esy-web.dev" + font-src: + - "'self'" + - "data:" + - 'https://fonts.gstatic.com' + - "https://cdnjs.cloudflare.com/" + frame-ancestors: ["'none'"] + # Optionnel : forcer le passage en HTTPS + upgrade-insecure-requests: false diff --git a/config/packages/packages/notifier.yaml b/config/packages/packages/notifier.yaml new file mode 100644 index 0000000..d02f986 --- /dev/null +++ b/config/packages/packages/notifier.yaml @@ -0,0 +1,12 @@ +framework: + notifier: + chatter_transports: + texter_transports: + channel_policy: + # use chat/slack, chat/telegram, sms/twilio or sms/nexmo + urgent: ['email'] + high: ['email'] + medium: ['email'] + low: ['email'] + admin_recipients: + - { email: admin@example.com } diff --git a/config/packages/packages/pixel_open_cloudflare_turnstile.yaml b/config/packages/packages/pixel_open_cloudflare_turnstile.yaml new file mode 100644 index 0000000..8953b28 --- /dev/null +++ b/config/packages/packages/pixel_open_cloudflare_turnstile.yaml @@ -0,0 +1,3 @@ +pixel_open_cloudflare_turnstile: + key: "%env(TURNSTILE_KEY)%" + secret: "%env(TURNSTILE_SECRET)%" diff --git a/config/packages/packages/presta_sitemap.yaml b/config/packages/packages/presta_sitemap.yaml new file mode 100644 index 0000000..eba4e5b --- /dev/null +++ b/config/packages/packages/presta_sitemap.yaml @@ -0,0 +1,3 @@ +presta_sitemap: + sitemap_file_prefix: 'sitemap' + timetolive: 3600 diff --git a/config/packages/packages/property_info.yaml b/config/packages/packages/property_info.yaml new file mode 100644 index 0000000..dd31b9d --- /dev/null +++ b/config/packages/packages/property_info.yaml @@ -0,0 +1,3 @@ +framework: + property_info: + with_constructor_extractor: true diff --git a/config/packages/packages/pwa.yaml b/config/packages/packages/pwa.yaml new file mode 100644 index 0000000..cbcb687 --- /dev/null +++ b/config/packages/packages/pwa.yaml @@ -0,0 +1,23 @@ +pwa: + asset_compiler: false # Default to true. Will change to false in 2.0.0. + image_processor: 'pwa.image_processor.gd' # Or 'pwa.image_processor.gd' + favicons: + enabled: true + src: '%kernel.project_dir%/public/assets/notif.png' + serviceworker: + enabled: true + scope: "/" + use_cache: false + skip_waiting: true + manifest: + enabled: true + name: "E-Cosplay" + short_name: "PWA" + start_url: "app_home" + display: "standalone" + background_color: "#ffffff" + theme_color: "#4285f4" + categories: ['games','multimedia','social networking'] + icons: + - src: '%kernel.project_dir%/public/assets/notif.png' + sizes: [192] diff --git a/config/packages/packages/routing.yaml b/config/packages/packages/routing.yaml new file mode 100644 index 0000000..8166181 --- /dev/null +++ b/config/packages/packages/routing.yaml @@ -0,0 +1,10 @@ +framework: + router: + # Configure how to generate URLs in non-HTTP contexts, such as CLI commands. + # See https://symfony.com/doc/current/routing.html#generating-urls-in-commands + #default_uri: http://localhost + +when@prod: + framework: + router: + strict_requirements: null diff --git a/config/packages/packages/security.yaml b/config/packages/packages/security.yaml new file mode 100644 index 0000000..4893398 --- /dev/null +++ b/config/packages/packages/security.yaml @@ -0,0 +1,50 @@ +# config/packages/security.yaml +security: + # https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers + providers: + # Appelle votre provider d'utilisateurs. + # Ici, nous configurons un provider d'entité pour notre classe Account, + # en spécifiant 'username' comme propriété d'identification. + app_account_provider: + entity: + class: App\Entity\Account + property: email # Utilise le champ 'username' de votre entité Account pour l'authentification + + firewalls: + dev: + pattern: ^/(_(profiler|wdt)|css|images|js)/ + security: false + + main: + lazy: true + provider: app_account_provider # Utilise le provider que nous avons défini ci-dessus + user_checker: App\Security\UserChecker + form_login: + login_path: app_login # La route vers votre formulaire de connexion (GET) + check_path: app_login # L'URL où le formulaire POST sera soumis + enable_csrf: true # Active la protection CSRF + csrf_token_id: authenticate # ID du jeton CSRF (doit correspondre à celui dans votre Twig) + entry_point: App\Security\AuthenticationEntryPoint + custom_authenticator: + - App\Security\LoginFormAuthenticator + - App\Security\KeycloakAuthenticator + - App\Security\DiscordAuthenticator + logout: + target: app_logout + + # Configuration des algorithmes de hachage des mots de passe. + # Symfony choisira automatiquement le meilleur algorithme par défaut si non spécifié, + # mais vous pouvez le configurer explicitement. + password_hashers: + App\Entity\Account: 'auto' # 'auto' sélectionne le meilleur algorithme disponible (recommandé) + # Ou pour spécifier bcrypt explicitement : + # App\Entity\Account: + # algorithm: bcrypt + + role_hierarchy: + ROLE_ROOT: [ROLE_ADMIN] # ROLE_ROOT inclut ROLE_ADMIN, qui à son tour inclut ROLE_ARTEMIS + + + access_control: + - { path: ^/admin, roles: [ROLE_ADMIN] } + - { path: ^/, roles: PUBLIC_ACCESS } # Toutes les autres pages nécessitent une authentification complète diff --git a/config/packages/packages/sentry.yaml b/config/packages/packages/sentry.yaml new file mode 100644 index 0000000..d6165de --- /dev/null +++ b/config/packages/packages/sentry.yaml @@ -0,0 +1,36 @@ +when@prod: + sentry: + dsn: '%env(SENTRY_DSN)%' + options: + # Add request headers, cookies, IP address and the authenticated user + # see https://docs.sentry.io/platforms/php/data-management/data-collected/ for more info + # send_default_pii: true + ignore_exceptions: + - 'Symfony\Component\ErrorHandler\Error\FatalError' + - 'Symfony\Component\Debug\Exception\FatalErrorException' +# +# # If you are using Monolog, you also need this additional configuration to log the errors correctly: +# # https://docs.sentry.io/platforms/php/guides/symfony/#monolog-integration +# register_error_listener: false +# register_error_handler: false +# +# monolog: +# handlers: +# # Use this only if you don't want to use structured logging and instead receive +# # certain log levels as errors. +# sentry: +# type: sentry +# level: !php/const Monolog\Logger::ERROR +# hub_id: Sentry\State\HubInterface +# fill_extra_context: true # Enables sending monolog context to Sentry +# process_psr_3_messages: false # Disables the resolution of PSR-3 placeholders +# +# # Use this for structured log integration +# sentry_logs: +# type: service +# id: Sentry\SentryBundle\Monolog\LogsHandler +# +# services: +# Sentry\SentryBundle\Monolog\LogsHandler: +# arguments: +# - !php/const Monolog\Logger::INFO diff --git a/config/packages/packages/stripe.yaml b/config/packages/packages/stripe.yaml new file mode 100644 index 0000000..1e075c6 --- /dev/null +++ b/config/packages/packages/stripe.yaml @@ -0,0 +1,7 @@ +services: + stripe.client: + class: 'Stripe\StripeClient' + arguments: + - '%env(STRIPE_SECRET_KEY)%' + + Stripe\StripeClient: '@stripe.client' diff --git a/config/packages/packages/translation.yaml b/config/packages/packages/translation.yaml new file mode 100644 index 0000000..1ca9555 --- /dev/null +++ b/config/packages/packages/translation.yaml @@ -0,0 +1,5 @@ +framework: + default_locale: fr + translator: + default_path: '%kernel.project_dir%/translations' + providers: diff --git a/config/packages/packages/twig.yaml b/config/packages/packages/twig.yaml new file mode 100644 index 0000000..7aacde5 --- /dev/null +++ b/config/packages/packages/twig.yaml @@ -0,0 +1,7 @@ +twig: + file_name_pattern: '*.twig' + form_themes: + - 'form_tailwind.twig' +when@test: + twig: + strict_variables: true diff --git a/config/packages/packages/validator.yaml b/config/packages/packages/validator.yaml new file mode 100644 index 0000000..dd47a6a --- /dev/null +++ b/config/packages/packages/validator.yaml @@ -0,0 +1,11 @@ +framework: + validation: + # Enables validator auto-mapping support. + # For instance, basic validation constraints will be inferred from Doctrine's metadata. + #auto_mapping: + # App\Entity\: [] + +when@test: + framework: + validation: + not_compromised_password: false diff --git a/config/packages/packages/vich_uploader.yaml b/config/packages/packages/vich_uploader.yaml new file mode 100644 index 0000000..32ded30 --- /dev/null +++ b/config/packages/packages/vich_uploader.yaml @@ -0,0 +1,96 @@ +vich_uploader: + db_driver: orm + mappings: + avatar: + uri_prefix: /storage/avatar + upload_destination: '%kernel.project_dir%/public/storage/avatar' + namer: App\VichUploader\Namer\Account\AvatarName # Replaced namer + directory_namer: App\VichUploader\DirectoryNamer\Account\AvatarName + inject_on_load: true + delete_on_update: true + delete_on_remove: true + members: + uri_prefix: /storage/members + upload_destination: '%kernel.project_dir%/public/storage/members' + namer: App\VichUploader\Namer\Account\AvatarName # Replaced namer + directory_namer: App\VichUploader\DirectoryNamer\Account\AvatarName + inject_on_load: true + delete_on_update: true + delete_on_remove: true + members_page_avatar: + uri_prefix: /storage/members_page_avatar + upload_destination: '%kernel.project_dir%/public/storage/members_page_avatar' + namer: Vich\UploaderBundle\Naming\UniqidNamer + inject_on_load: true + delete_on_update: true + delete_on_remove: true + members_page_cosplay: + uri_prefix: /storage/members_page_avatar + upload_destination: '%kernel.project_dir%/public/storage/members_page_avatar' + namer: Vich\UploaderBundle\Naming\UniqidNamer + inject_on_load: true + delete_on_update: true + delete_on_remove: true + members_page_cosplay_details: + uri_prefix: /storage/members_page_cosplay_details + upload_destination: '%kernel.project_dir%/public/storage/members_page_cosplay_details' + namer: Vich\UploaderBundle\Naming\UniqidNamer + inject_on_load: true + delete_on_update: true + delete_on_remove: true + ag: + uri_prefix: /storage/ag + upload_destination: '%kernel.project_dir%/public/storage/ag' + namer: Vich\UploaderBundle\Naming\UniqidNamer # Replaced namer + inject_on_load: true + delete_on_update: true + delete_on_remove: true + ag_adh: + uri_prefix: /storage/ag_adh + upload_destination: '%kernel.project_dir%/public/storage/ag_adh' + namer: Vich\UploaderBundle\Naming\UniqidNamer # Replaced namer + inject_on_load: true + delete_on_update: true + delete_on_remove: true + product: + uri_prefix: /storage/product + upload_destination: '%kernel.project_dir%/public/storage/product' + namer: Vich\UploaderBundle\Naming\UniqidNamer # Replaced namer + inject_on_load: true + delete_on_update: true + delete_on_remove: true + events: + uri_prefix: /storage/events + upload_destination: '%kernel.project_dir%/public/storage/events' + namer: Vich\UploaderBundle\Naming\UniqidNamer # Replaced namer + inject_on_load: true + delete_on_update: true + delete_on_remove: true + event_picture: + uri_prefix: /event_picture/events + upload_destination: '%kernel.project_dir%/public/storage/event_picture' + namer: Vich\UploaderBundle\Naming\UniqidNamer # Replaced namer + directory_namer: App\VichUploader\DirectoryNamer\EventName + inject_on_load: true + delete_on_update: true + delete_on_remove: true + epage_avatar: + uri_prefix: /storage/epage/ + upload_destination: '%kernel.project_dir%/public/storage/epage' + namer: App\VichUploader\Namer\Epage\AvatarNamer # Replaced namer + directory_namer: App\VichUploader\DirectoryNamer\Epage\DirectoryNamer + inject_on_load: true + delete_on_update: true + delete_on_remove: true + fiche: + uri_prefix: /storage/fiche_candidat + upload_destination: '%kernel.project_dir%/public/storage/fiche_candidat' + namer: Vich\UploaderBundle\Naming\UniqidNamer # Replaced namer + inject_on_load: true + delete_on_update: true + delete_on_remove: true + #mappings: + # products: + # uri_prefix: /images/products + # upload_destination: '%kernel.project_dir%/public/images/products' + # namer: Vich\UploaderBundle\Naming\SmartUniqueNamer diff --git a/config/packages/packages/web_profiler.yaml b/config/packages/packages/web_profiler.yaml new file mode 100644 index 0000000..4f4053b --- /dev/null +++ b/config/packages/packages/web_profiler.yaml @@ -0,0 +1,13 @@ +when@dev: + web_profiler: + toolbar: true + intercept_redirects: false + framework: + profiler: + collect_serializer_data: true + +when@test: + framework: + profiler: + collect: false + collect_serializer_data: true diff --git a/config/packages/prod/nelmio_security.yaml b/config/packages/prod/nelmio_security.yaml new file mode 100644 index 0000000..06d4663 --- /dev/null +++ b/config/packages/prod/nelmio_security.yaml @@ -0,0 +1,22 @@ +nelmio_security: + csp: + enforce: + script-src: + - 'self' + - 'nonce' + - 'https://static.cloudflareinsights.com' + - 'https://challenges.cloudflare.com' + + # Restreindre les soumissions de formulaires à notre domaine + # et aux redirections OAuth des plateformes de partage social + form-action: + - 'self' + - 'https://www.facebook.com' + - 'https://x.com' + - 'https://twitter.com' + + # Autoriser navigator.share() (Web Share API) et clipboard API + # — les deux sont des APIs navigateur natives, pas des appels réseau externes + # Ce bloc est présent pour documentation et futures intégrations + connect-src: + - 'self' diff --git a/config/packages/pwa.yaml b/config/packages/pwa.yaml new file mode 100644 index 0000000..3dfdd34 --- /dev/null +++ b/config/packages/pwa.yaml @@ -0,0 +1,8 @@ +# Documentation available at https://pwa.spomky-labs.com/ +## Please add the favicons, manifest or serviceworker sections depending on your needs. +pwa: + asset_compiler: false + image_processor: 'pwa.image_processor.gd' + favicons: + enabled: true + src: '%kernel.project_dir%/public/favicon.png' diff --git a/config/packages/scheb_2fa.yaml b/config/packages/scheb_2fa.yaml new file mode 100644 index 0000000..8b064c6 --- /dev/null +++ b/config/packages/scheb_2fa.yaml @@ -0,0 +1,20 @@ +scheb_two_factor: + security_tokens: + - Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken + - Symfony\Component\Security\Http\Authenticator\Token\PostAuthenticationToken + + email: + enabled: true + sender_email: 'contact@e-cosplay.fr' + sender_name: 'CRM Ecosplay' + digits: 6 + template: 'security/2fa_email.html.twig' + mailer: App\Security\TwoFactorCodeMailer + + google: + enabled: true + issuer: 'CRM Ecosplay' + template: 'security/2fa_google.html.twig' + + backup_codes: + enabled: true diff --git a/config/packages/security.yaml b/config/packages/security.yaml index 8964044..5578d1e 100644 --- a/config/packages/security.yaml +++ b/config/packages/security.yaml @@ -5,27 +5,53 @@ security: # https://symfony.com/doc/current/security.html#loading-the-user-the-user-provider providers: - users_in_memory: { memory: null } + app_user_provider: + entity: + class: App\Entity\User + property: email firewalls: dev: - # Ensure dev tools and static assets are always allowed pattern: ^/(_profiler|_wdt|assets|build)/ security: false + webhooks: + pattern: ^/webhooks/ + security: false main: lazy: true - provider: users_in_memory + provider: app_user_provider + entry_point: form_login + custom_authenticators: + - App\Security\KeycloakAuthenticator + form_login: + login_path: app_home + check_path: app_home + username_parameter: _username + password_parameter: _password + success_handler: App\Security\LoginSuccessHandler + logout: + path: app_logout + two_factor: + auth_form_path: 2fa_login + check_path: 2fa_login_check + default_target_path: app_home + prepare_on_login: true + prepare_on_access_denied: true + enable_csrf: true - # Activate different ways to authenticate: - # https://symfony.com/doc/current/security.html#the-firewall - - # https://symfony.com/doc/current/security/impersonating_user.html - # switch_user: true + role_hierarchy: + ROLE_CUSTOMER: ROLE_USER + ROLE_REVENDEUR: ROLE_CUSTOMER + ROLE_MEMBER: ROLE_USER + ROLE_EMPLOYE: ROLE_MEMBER + ROLE_ROOT: [ROLE_EMPLOYE, ROLE_REVENDEUR] # Note: Only the *first* matching rule is applied access_control: - # - { path: ^/admin, roles: ROLE_ADMIN } - # - { path: ^/profile, roles: ROLE_USER } + - { path: ^/2fa, role: IS_AUTHENTICATED_2FA_IN_PROGRESS } + - { path: ^/admin, roles: ROLE_EMPLOYE } + - { path: ^/espace-client, roles: ROLE_CUSTOMER } + - { path: ^/espace-prestataire, roles: ROLE_REVENDEUR } when@test: security: diff --git a/config/packages/stripe.yaml b/config/packages/stripe.yaml new file mode 100644 index 0000000..1e075c6 --- /dev/null +++ b/config/packages/stripe.yaml @@ -0,0 +1,7 @@ +services: + stripe.client: + class: 'Stripe\StripeClient' + arguments: + - '%env(STRIPE_SECRET_KEY)%' + + Stripe\StripeClient: '@stripe.client' diff --git a/config/packages/ux_turbo.yaml b/config/packages/ux_turbo.yaml deleted file mode 100644 index c2a6a44..0000000 --- a/config/packages/ux_turbo.yaml +++ /dev/null @@ -1,4 +0,0 @@ -# Enable stateless CSRF protection for forms and logins/logouts -framework: - csrf_protection: - check_header: true diff --git a/config/packages/vich_uploader.yaml b/config/packages/vich_uploader.yaml new file mode 100644 index 0000000..0ca283a --- /dev/null +++ b/config/packages/vich_uploader.yaml @@ -0,0 +1,8 @@ +vich_uploader: + db_driver: orm + + mappings: + user_avatar: + uri_prefix: /uploads/avatars + upload_destination: '%kernel.project_dir%/public/uploads/avatars' + namer: Vich\UploaderBundle\Naming\SmartUniqueNamer diff --git a/config/reference.php b/config/reference.php index 803d936..b30abce 100644 --- a/config/reference.php +++ b/config/reference.php @@ -622,7 +622,7 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param; * }>, * }, * rate_limiter?: bool|array{ // Rate limiter configuration - * enabled?: bool|Param, // Default: false + * enabled?: bool|Param, // Default: true * limiters?: array, - * controllers_json?: scalar|Param|null, // Default: "%kernel.project_dir%/assets/controllers.json" - * } - * @psalm-type TurboConfig = array{ - * broadcast?: bool|array{ - * enabled?: bool|Param, // Default: true - * entity_template_prefixes?: list, - * doctrine_orm?: bool|array{ // Enable the Doctrine ORM integration - * enabled?: bool|Param, // Default: true - * }, - * }, - * default_transport?: scalar|Param|null, // Default: "default" - * } * @psalm-type TwigExtraConfig = array{ * cache?: bool|array{ * enabled?: bool|Param, // Default: false @@ -966,10 +952,10 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param; * enabled?: bool|Param, // Default: false * }, * cssinliner?: bool|array{ - * enabled?: bool|Param, // Default: false + * enabled?: bool|Param, // Default: true * }, * inky?: bool|array{ - * enabled?: bool|Param, // Default: false + * enabled?: bool|Param, // Default: true * }, * string?: bool|array{ * enabled?: bool|Param, // Default: false @@ -1291,6 +1277,28 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param; * always_remember_me?: bool|Param, // Default: false * remember_me_parameter?: scalar|Param|null, // Default: "_remember_me" * }, + * two_factor?: array{ + * check_path?: scalar|Param|null, // Default: "/2fa_check" + * post_only?: bool|Param, // Default: true + * auth_form_path?: scalar|Param|null, // Default: "/2fa" + * always_use_default_target_path?: bool|Param, // Default: false + * default_target_path?: scalar|Param|null, // Default: "/" + * success_handler?: scalar|Param|null, // Default: null + * failure_handler?: scalar|Param|null, // Default: null + * authentication_required_handler?: scalar|Param|null, // Default: null + * auth_code_parameter_name?: scalar|Param|null, // Default: "_auth_code" + * trusted_parameter_name?: scalar|Param|null, // Default: "_trusted" + * remember_me_sets_trusted?: scalar|Param|null, // Default: false + * multi_factor?: bool|Param, // Default: false + * prepare_on_login?: bool|Param, // Default: false + * prepare_on_access_denied?: bool|Param, // Default: false + * enable_csrf?: scalar|Param|null, // Default: false + * csrf_parameter?: scalar|Param|null, // Default: "_csrf_token" + * csrf_token_id?: scalar|Param|null, // Default: "two_factor" + * csrf_header?: scalar|Param|null, // Default: null + * csrf_token_manager?: scalar|Param|null, // Default: "scheb_two_factor.csrf_token_manager" + * provider?: scalar|Param|null, // Default: null + * }, * }>, * access_control?: list + * @psalm-type KnpPaginatorConfig = array{ + * default_options?: array{ + * sort_field_name?: scalar|Param|null, // Default: "sort" + * sort_direction_name?: scalar|Param|null, // Default: "direction" + * filter_field_name?: scalar|Param|null, // Default: "filterField" + * filter_value_name?: scalar|Param|null, // Default: "filterValue" + * page_name?: scalar|Param|null, // Default: "page" + * distinct?: bool|Param, // Default: true + * page_out_of_range?: scalar|Param|null, // Default: "ignore" + * default_limit?: scalar|Param|null, // Default: 10 + * }, + * template?: array{ + * pagination?: scalar|Param|null, // Default: "@KnpPaginator/Pagination/sliding.html.twig" + * rel_links?: scalar|Param|null, // Default: "@KnpPaginator/Pagination/rel_links.html.twig" + * filtration?: scalar|Param|null, // Default: "@KnpPaginator/Pagination/filtration.html.twig" + * sortable?: scalar|Param|null, // Default: "@KnpPaginator/Pagination/sortable_link.html.twig" + * }, + * page_range?: scalar|Param|null, // Default: 5 + * page_limit?: scalar|Param|null, // Default: null + * convert_exception?: bool|Param, // Default: false + * remove_first_page_param?: bool|Param, // Default: false + * } + * @psalm-type KnpuOauth2ClientConfig = array{ + * http_client?: scalar|Param|null, // Service id of HTTP client to use (must implement GuzzleHttp\ClientInterface) // Default: null + * http_client_options?: array{ + * timeout?: int|Param, + * proxy?: scalar|Param|null, + * verify?: bool|Param, // Use only with proxy option set + * }, + * clients?: array>, + * } + * @psalm-type FlysystemConfig = array{ + * storages?: array, + * visibility?: scalar|Param|null, // Default: null + * directory_visibility?: scalar|Param|null, // Default: null + * retain_visibility?: bool|Param|null, // Default: null + * case_sensitive?: bool|Param, // Default: true + * disable_asserts?: bool|Param, // Default: false + * public_url?: list, + * path_normalizer?: scalar|Param|null, // Default: null + * public_url_generator?: scalar|Param|null, // Default: null + * temporary_url_generator?: scalar|Param|null, // Default: null + * read_only?: bool|Param, // Default: false + * }>, + * } + * @psalm-type LiipImagineConfig = array{ + * resolvers?: array, + * get_options?: array, + * put_options?: array, + * proxies?: array, + * }, + * flysystem?: array{ + * filesystem_service?: scalar|Param|null, + * cache_prefix?: scalar|Param|null, // Default: "" + * root_url?: scalar|Param|null, + * visibility?: "public"|"private"|"noPredefinedVisibility"|Param, // Default: "public" + * }, + * }>, + * loaders?: array, + * allow_unresolvable_data_roots?: bool|Param, // Default: false + * bundle_resources?: array{ + * enabled?: bool|Param, // Default: false + * access_control_type?: "blacklist"|"whitelist"|Param, // Sets the access control method applied to bundle names in "access_control_list" into a blacklist or whitelist. // Default: "blacklist" + * access_control_list?: list, + * }, + * }, + * flysystem?: array{ + * filesystem_service?: scalar|Param|null, + * }, + * asset_mapper?: array, + * chain?: array{ + * loaders?: list, + * }, + * }>, + * driver?: scalar|Param|null, // Default: "gd" + * cache?: scalar|Param|null, // Default: "default" + * cache_base_path?: scalar|Param|null, // Default: "" + * data_loader?: scalar|Param|null, // Default: "default" + * default_image?: scalar|Param|null, // Default: null + * default_filter_set_settings?: array{ + * quality?: scalar|Param|null, // Default: 100 + * jpeg_quality?: scalar|Param|null, // Default: null + * png_compression_level?: scalar|Param|null, // Default: null + * png_compression_filter?: scalar|Param|null, // Default: null + * format?: scalar|Param|null, // Default: null + * animated?: bool|Param, // Default: false + * cache?: scalar|Param|null, // Default: null + * data_loader?: scalar|Param|null, // Default: null + * default_image?: scalar|Param|null, // Default: null + * filters?: array>, + * post_processors?: array>, + * }, + * controller?: array{ + * filter_action?: scalar|Param|null, // Default: "Liip\\ImagineBundle\\Controller\\ImagineController::filterAction" + * filter_runtime_action?: scalar|Param|null, // Default: "Liip\\ImagineBundle\\Controller\\ImagineController::filterRuntimeAction" + * redirect_response_code?: int|Param, // Default: 302 + * }, + * filter_sets?: array>, + * post_processors?: array>, + * }>, + * twig?: array{ + * mode?: "none"|"lazy"|"legacy"|Param, // Twig mode: none/lazy/legacy (default) // Default: "legacy" + * assets_version?: scalar|Param|null, // Default: null + * }, + * enqueue?: bool|Param, // Enables integration with enqueue if set true. Allows resolve image caches in background by sending messages to MQ. // Default: false + * messenger?: bool|array{ // Enables integration with symfony/messenger if set true. Warmup image caches in background by sending messages to MQ. + * enabled?: bool|Param, // Default: false + * }, + * templating?: bool|Param, // Enables integration with symfony/templating component // Default: true + * webp?: array{ + * generate?: bool|Param, // Default: false + * quality?: int|Param, // Default: 100 + * cache?: scalar|Param|null, // Default: null + * data_loader?: scalar|Param|null, // Default: null + * post_processors?: array>, + * }, + * } + * @psalm-type NelmioSecurityConfig = array{ + * signed_cookie?: array{ + * names?: list, + * secret?: scalar|Param|null, // Default: "%kernel.secret%" + * hash_algo?: scalar|Param|null, + * legacy_hash_algo?: scalar|Param|null, // Fallback algorithm to allow for frictionless hash algorithm upgrades. Use with caution and as a temporary measure as it allows for downgrade attacks. // Default: null + * separator?: scalar|Param|null, // Default: "." + * }, + * clickjacking?: array{ + * hosts?: list, + * paths?: array, + * content_types?: list, + * }, + * external_redirects?: array{ + * abort?: bool|Param, // Default: false + * override?: scalar|Param|null, // Default: null + * forward_as?: scalar|Param|null, // Default: null + * log?: bool|Param, // Default: false + * allow_list?: list, + * }, + * flexible_ssl?: bool|array{ + * enabled?: bool|Param, // Default: false + * cookie_name?: scalar|Param|null, // Default: "auth" + * unsecured_logout?: bool|Param, // Default: false + * }, + * forced_ssl?: bool|array{ + * enabled?: bool|Param, // Default: false + * hsts_max_age?: scalar|Param|null, // Default: null + * hsts_subdomains?: bool|Param, // Default: false + * hsts_preload?: bool|Param, // Default: false + * allow_list?: list, + * hosts?: list, + * redirect_status_code?: scalar|Param|null, // Default: 302 + * }, + * content_type?: array{ + * nosniff?: bool|Param, // Default: false + * }, + * xss_protection?: array{ // Deprecated: The "xss_protection" option is deprecated, use Content Security Policy without allowing "unsafe-inline" scripts instead. + * enabled?: bool|Param, // Default: false + * mode_block?: bool|Param, // Default: false + * report_uri?: scalar|Param|null, // Default: null + * }, + * csp?: bool|array{ + * enabled?: bool|Param, // Default: true + * request_matcher?: scalar|Param|null, // Default: null + * hosts?: list, + * content_types?: list, + * report_endpoint?: array{ + * log_channel?: scalar|Param|null, // Default: null + * log_formatter?: scalar|Param|null, // Default: "nelmio_security.csp_report.log_formatter" + * log_level?: "alert"|"critical"|"debug"|"emergency"|"error"|"info"|"notice"|"warning"|Param, // Default: "notice" + * filters?: array{ + * domains?: bool|Param, // Default: true + * schemes?: bool|Param, // Default: true + * browser_bugs?: bool|Param, // Default: true + * injected_scripts?: bool|Param, // Default: true + * }, + * dismiss?: list>, + * }, + * compat_headers?: bool|Param, // Default: true + * report_logger_service?: scalar|Param|null, // Default: "logger" + * hash?: array{ + * algorithm?: "sha256"|"sha384"|"sha512"|Param, // The algorithm to use for hashes // Default: "sha256" + * }, + * report?: array{ + * level1_fallback?: bool|Param, // Provides CSP Level 1 fallback when using hash or nonce (CSP level 2) by adding 'unsafe-inline' source. See https://www.w3.org/TR/CSP2/#directive-script-src and https://www.w3.org/TR/CSP2/#directive-style-src // Default: true + * browser_adaptive?: bool|array{ // Do not send directives that browser do not support + * enabled?: bool|Param, // Default: false + * parser?: scalar|Param|null, // Default: "nelmio_security.ua_parser.ua_php" + * }, + * default-src?: list, + * base-uri?: list, + * block-all-mixed-content?: bool|Param, // Default: false + * child-src?: list, + * connect-src?: list, + * font-src?: list, + * form-action?: list, + * frame-ancestors?: list, + * frame-src?: list, + * img-src?: list, + * manifest-src?: list, + * media-src?: list, + * object-src?: list, + * plugin-types?: list, + * script-src?: list, + * style-src?: list, + * upgrade-insecure-requests?: bool|Param, // Default: false + * report-uri?: list, + * worker-src?: list, + * prefetch-src?: list, + * report-to?: scalar|Param|null, + * }, + * enforce?: array{ + * level1_fallback?: bool|Param, // Provides CSP Level 1 fallback when using hash or nonce (CSP level 2) by adding 'unsafe-inline' source. See https://www.w3.org/TR/CSP2/#directive-script-src and https://www.w3.org/TR/CSP2/#directive-style-src // Default: true + * browser_adaptive?: bool|array{ // Do not send directives that browser do not support + * enabled?: bool|Param, // Default: false + * parser?: scalar|Param|null, // Default: "nelmio_security.ua_parser.ua_php" + * }, + * default-src?: list, + * base-uri?: list, + * block-all-mixed-content?: bool|Param, // Default: false + * child-src?: list, + * connect-src?: list, + * font-src?: list, + * form-action?: list, + * frame-ancestors?: list, + * frame-src?: list, + * img-src?: list, + * manifest-src?: list, + * media-src?: list, + * object-src?: list, + * plugin-types?: list, + * script-src?: list, + * style-src?: list, + * upgrade-insecure-requests?: bool|Param, // Default: false + * report-uri?: list, + * worker-src?: list, + * prefetch-src?: list, + * report-to?: scalar|Param|null, + * }, + * }, + * referrer_policy?: bool|array{ + * enabled?: bool|Param, // Default: false + * policies?: list, + * }, + * permissions_policy?: bool|array{ + * enabled?: bool|Param, // Default: false + * policies?: array{ + * accelerometer?: mixed, // Default: null + * ambient_light_sensor?: mixed, // Default: null + * attribution_reporting?: mixed, // Default: null + * autoplay?: mixed, // Default: null + * bluetooth?: mixed, // Default: null + * browsing_topics?: mixed, // Default: null + * camera?: mixed, // Default: null + * captured_surface_control?: mixed, // Default: null + * compute_pressure?: mixed, // Default: null + * cross_origin_isolated?: mixed, // Default: null + * deferred_fetch?: mixed, // Default: null + * deferred_fetch_minimal?: mixed, // Default: null + * display_capture?: mixed, // Default: null + * encrypted_media?: mixed, // Default: null + * fullscreen?: mixed, // Default: null + * gamepad?: mixed, // Default: null + * geolocation?: mixed, // Default: null + * gyroscope?: mixed, // Default: null + * hid?: mixed, // Default: null + * identity_credentials_get?: mixed, // Default: null + * idle_detection?: mixed, // Default: null + * interest_cohort?: mixed, // Default: null + * language_detector?: mixed, // Default: null + * local_fonts?: mixed, // Default: null + * magnetometer?: mixed, // Default: null + * microphone?: mixed, // Default: null + * midi?: mixed, // Default: null + * otp_credentials?: mixed, // Default: null + * payment?: mixed, // Default: null + * picture_in_picture?: mixed, // Default: null + * publickey_credentials_create?: mixed, // Default: null + * publickey_credentials_get?: mixed, // Default: null + * screen_wake_lock?: mixed, // Default: null + * serial?: mixed, // Default: null + * speaker_selection?: mixed, // Default: null + * storage_access?: mixed, // Default: null + * summarizer?: mixed, // Default: null + * translator?: mixed, // Default: null + * usb?: mixed, // Default: null + * web_share?: mixed, // Default: null + * window_management?: mixed, // Default: null + * xr_spatial_tracking?: mixed, // Default: null + * }, + * }, + * cross_origin_isolation?: bool|array{ + * enabled?: bool|Param, // Default: false + * paths?: array, + * }, + * } + * @psalm-type PwaConfig = array{ + * asset_compiler?: bool|Param, // When true, the assets will be compiled when the command "asset-map:compile" is run. // Default: true + * early_hints?: bool|array{ // Early Hints (HTTP 103) configuration. Requires a compatible server (FrankenPHP, Caddy). + * enabled?: bool|Param, // Default: false + * preload_manifest?: bool|Param, // Preload the PWA manifest file. // Default: true + * preload_serviceworker?: bool|Param, // Preload the service worker script. Disabled by default as SW registration is usually deferred. // Default: false + * preconnect_workbox_cdn?: bool|Param, // Preconnect to Workbox CDN when using CDN mode. // Default: true + * }, + * favicons?: bool|array{ + * enabled?: bool|Param, // Default: false + * default?: array{ // The favicon source and parameters. When used with "dark", this favicon will become the light version. + * src?: scalar|Param|null, // The path to the icon. Can be served by Asset Mapper, an absolute path or a Symfony UX Icon (if the bundle is installed). + * background_color?: scalar|Param|null, // The background color of the application. If this value is not defined and that of the Manifest section is, the value of the latter will be used. // Default: null + * border_radius?: int|Param, // The border radius of the icon. // Default: null + * image_scale?: int|Param, // The scale of the icon. // Default: null + * svg_attr?: array, + * }, + * dark?: array{ // The favicon source and parameters for the dark theme. Should only be used with "default". + * src?: scalar|Param|null, // The path to the icon. Can be served by Asset Mapper, an absolute path or a Symfony UX Icon (if the bundle is installed). + * background_color?: scalar|Param|null, // The background color of the application. If this value is not defined and that of the Manifest section is, the value of the latter will be used. // Default: null + * border_radius?: int|Param, // The border radius of the icon. // Default: null + * image_scale?: int|Param, // The scale of the icon. // Default: null + * svg_attr?: array, + * }, + * src?: scalar|Param|null, // Deprecated: The "src" configuration key is deprecated. Use the "default.src" configuration key instead. // The source of the favicon. Shall be a SVG or large PNG. // Default: null + * src_dark?: scalar|Param|null, // Deprecated: The "src_dark" configuration key is deprecated. Use the "dark.src" configuration key instead. // The source of the favicon in dark mode. Shall be a SVG or large PNG. // Default: null + * background_color?: scalar|Param|null, // Deprecated: The "background_color" configuration key is deprecated. Use the "default.background_color" configuration key instead. // The background color of the icon. // Default: null + * background_color_dark?: scalar|Param|null, // Deprecated: The "background_color_dark" configuration key is deprecated. Use the "dark.background_color" configuration key instead. // The background color of the icon in dark mode. // Default: null + * safari_pinned_tab_color?: scalar|Param|null, // The color of the Safari pinned tab. Requires "use_silhouette" to be set to "true". // Default: null + * tile_color?: scalar|Param|null, // The color of the tile for Windows 8+. // Default: null + * border_radius?: int|Param, // Deprecated: The "border_radius" configuration key is deprecated. Use the "default.border_radius" or "dark.border_radius" configuration key instead. // The border radius of the icon. // Default: null + * image_scale?: int|Param, // Deprecated: The "image_scale" configuration key is deprecated. Use the "default.image_scale" or "dark.image_scale" configuration key instead. // The scale of the icon. // Default: null + * low_resolution?: bool|Param, // Include low resolution icons. // Default: false + * use_silhouette?: bool|Param|null, // Use only the silhouette of the icon. Applicable for macOS Safari and Windows 8+. Requires potrace to be installed. // Default: null + * use_start_image?: bool|Param, // Use the icon as a start image for the iOS splash screen. // Default: true + * svg_color?: scalar|Param|null, // When the asset is a SVG file, replaces the currentColor attribute with this color. // Default: "#000" + * monochrome?: bool|Param, // Use a monochrome icon. // Default: false + * potrace?: scalar|Param|null, // The path to the potrace binary. // Default: "potrace" + * }, + * image_processor?: scalar|Param|null, // The image processor to use to generate the icons of different sizes. // Default: null + * logger?: scalar|Param|null, // The logger service to use. If not set, the default logger will be used. // Default: null + * manifest?: bool|array{ + * enabled?: bool|Param, // Default: false + * public_url?: scalar|Param|null, // The public URL of the manifest file. // Default: "/site.webmanifest" + * use_credentials?: bool|Param, // Indicates whether the manifest should be fetched with credentials. // Default: true + * background_color?: scalar|Param|null, // The background color of the application. It should match the background-color CSS property in the sites stylesheet for a smooth transition between launching the web application and loading the site's content. + * categories?: list, + * description?: scalar|Param|null, // The description of the application. + * display?: scalar|Param|null, // The display mode of the application. + * display_override?: list, + * id?: scalar|Param|null, // A string that represents the identity of the web application. + * orientation?: scalar|Param|null, // The orientation of the application. + * dir?: scalar|Param|null, // The direction of the application. + * lang?: scalar|Param|null, // The language of the application. + * name?: scalar|Param|null, // The name of the application. + * short_name?: scalar|Param|null, // The short name of the application. + * scope?: scalar|Param|null, // The scope of the application. + * start_url?: string|array{ // The start URL of the application. + * path?: scalar|Param|null, // The URL or route name. + * path_type_reference?: int|Param, // The path type reference to generate paths/URLs. See https://symfony.com/doc/current/routing.html#generating-urls-in-controllers for more information. // Default: 1 + * params?: list, + * }, + * theme_color?: scalar|Param|null, // The theme color of the application. If a dark theme color is specified, the theme color will be used for the light theme. + * dark_theme_color?: scalar|Param|null, // The dark theme color of the application. + * edge_side_panel?: array{ // Specifies whether or not your app supports the side panel view in Microsoft Edge. + * preferred_width?: int|Param, // Specifies the preferred width of the side panel view in Microsoft Edge. + * }, + * iarc_rating_id?: scalar|Param|null, // Specifies the International Age Rating Coalition (IARC) rating ID for the app. See https://www.globalratings.com/how-iarc-works.aspx for more information. + * scope_extensions?: list, + * handle_links?: scalar|Param|null, // Specifies the default link handling for the web app. + * note_taking?: array{ // The note-taking capabilities of the application. + * note_taking_url?: string|array{ // The URL to the note-taking service. + * path?: scalar|Param|null, // The URL or route name. + * path_type_reference?: int|Param, // The path type reference to generate paths/URLs. See https://symfony.com/doc/current/routing.html#generating-urls-in-controllers for more information. // Default: 1 + * params?: list, + * }, + * }, + * icons?: list, + * background_color?: scalar|Param|null, // The background color of the application. If this value is not defined and that of the Manifest section is, the value of the latter will be used. // Default: null + * border_radius?: int|Param, // The border radius of the icon. // Default: null + * image_scale?: int|Param, // The scale of the icon. // Default: null + * type?: scalar|Param|null, // The icon mime type. + * format?: scalar|Param|null, // The icon format. When set, the "type" option is ignored and the image will be converted. + * purpose?: scalar|Param|null, // The purpose of the icon. + * svg_attr?: array, + * }>, + * screenshots?: list, + * file_handlers?: list, + * }, + * accept?: array>, + * }>, + * launch_handler?: array{ // The launch handler of the application. + * client_mode?: list, + * }, + * protocol_handlers?: list, + * }, + * }>, + * prefer_related_applications?: bool|Param, // prefer related native applications (instead of this application) // Default: false + * related_applications?: list, + * }, + * id?: scalar|Param|null, // The ID of the application. + * }>, + * shortcuts?: list, + * }, + * icons?: list, + * background_color?: scalar|Param|null, // The background color of the application. If this value is not defined and that of the Manifest section is, the value of the latter will be used. // Default: null + * border_radius?: int|Param, // The border radius of the icon. // Default: null + * image_scale?: int|Param, // The scale of the icon. // Default: null + * type?: scalar|Param|null, // The icon mime type. + * format?: scalar|Param|null, // The icon format. When set, the "type" option is ignored and the image will be converted. + * purpose?: scalar|Param|null, // The purpose of the icon. + * svg_attr?: array, + * }>, + * }>, + * share_target?: array{ // The share target of the application. + * action?: string|array{ // The action of the share target. + * path?: scalar|Param|null, // The URL or route name. + * path_type_reference?: int|Param, // The path type reference to generate paths/URLs. See https://symfony.com/doc/current/routing.html#generating-urls-in-controllers for more information. // Default: 1 + * params?: list, + * }, + * method?: scalar|Param|null, // The method of the share target. + * enctype?: scalar|Param|null, // The enctype of the share target. Ignored if method is GET. + * params?: array{ // The parameters of the share target. + * title?: scalar|Param|null, // The title of the share target. + * text?: scalar|Param|null, // The text of the share target. + * url?: scalar|Param|null, // The URL of the share target. + * files?: list, + * }>, + * }, + * }, + * widgets?: list, + * background_color?: scalar|Param|null, // The background color of the application. If this value is not defined and that of the Manifest section is, the value of the latter will be used. // Default: null + * border_radius?: int|Param, // The border radius of the icon. // Default: null + * image_scale?: int|Param, // The scale of the icon. // Default: null + * type?: scalar|Param|null, // The icon mime type. + * format?: scalar|Param|null, // The icon format. When set, the "type" option is ignored and the image will be converted. + * purpose?: scalar|Param|null, // The purpose of the icon. + * svg_attr?: array, + * }>, + * screenshots?: list, + * tag?: scalar|Param|null, // A string used to reference the widget in the PWA service worker. + * template?: scalar|Param|null, // The template to use to display the widget in the operating system widgets dashboard. Note: this property is currently only informational and not used. See ms_ac_template below. + * ms_ac_template?: string|array{ // The URL of the custom Adaptive Cards template to use to display the widget in the operating system widgets dashboard. + * path?: scalar|Param|null, // The URL or route name. + * path_type_reference?: int|Param, // The path type reference to generate paths/URLs. See https://symfony.com/doc/current/routing.html#generating-urls-in-controllers for more information. // Default: 1 + * params?: list, + * }, + * data?: string|array{ // The URL where the data to fill the template with can be found. If present, this URL is required to return valid JSON. + * path?: scalar|Param|null, // The URL or route name. + * path_type_reference?: int|Param, // The path type reference to generate paths/URLs. See https://symfony.com/doc/current/routing.html#generating-urls-in-controllers for more information. // Default: 1 + * params?: list, + * }, + * type?: scalar|Param|null, // The MIME type for the widget data. + * auth?: bool|Param, // A boolean indicating if the widget requires authentication. + * update?: int|Param, // The frequency, in seconds, at which the widget will be updated. Code in your service worker must perform the updating; the widget is not updated automatically. See Access widget instances at runtime. + * multiple?: bool|Param, // A boolean indicating whether to allow multiple instances of the widget. Defaults to true. // Default: true + * }>, + * }, + * path_type_reference?: int|Param, // Deprecated: The "path_type_reference" configuration key is deprecated. Use the "path_type_reference" of URL nodes instead. // The path type reference to generate paths/URLs. See https://symfony.com/doc/current/routing.html#generating-urls-in-controllers for more information. // Default: 1 + * resource_hints?: bool|array{ // Resource Hints configuration for preconnect, dns-prefetch, and preload. + * enabled?: bool|Param, // Default: false + * auto_preconnect?: bool|Param, // Automatically add preconnect hints for detected external origins (Workbox CDN, Google Fonts). // Default: true + * preconnect?: list, + * dns_prefetch?: list, + * preload?: list, + * }, + * serviceworker?: bool|string|array{ + * enabled?: bool|Param, // Default: false + * src?: scalar|Param|null, // The path to the service worker source file. Can be served by Asset Mapper. + * dest?: scalar|Param|null, // The public URL to the service worker. // Default: "/sw.js" + * skip_waiting?: bool|Param, // Whether to skip waiting for the service worker to be activated. // Default: false + * scope?: scalar|Param|null, // The scope of the service worker. // Default: "/" + * use_cache?: bool|Param, // Whether the service worker should use the cache. // Default: true + * workbox?: bool|array{ // The configuration of the workbox. + * enabled?: bool|Param, // Default: true + * use_cdn?: bool|Param, // Deprecated: The "use_cdn" option is deprecated and will be removed in 2.0.0. use "config.use_cdn" instead. // Whether to use the local workbox or the CDN. // Default: false + * google_fonts?: bool|array{ + * enabled?: bool|Param, // Default: true + * cache_prefix?: scalar|Param|null, // The cache prefix for the Google fonts. // Default: null + * max_age?: scalar|Param|null, // The maximum age of the Google fonts cache (in seconds). // Default: null + * max_entries?: int|Param, // The maximum number of entries in the Google fonts cache. // Default: null + * }, + * cache_manifest?: bool|Param, // Whether to cache the manifest file. // Default: true + * version?: scalar|Param|null, // Deprecated: The "version" option is deprecated and will be removed in 2.0.0. use "config.version" instead. // The version of workbox. When using local files, the version shall be "7.0.0." // Default: "7.3.0" + * workbox_public_url?: scalar|Param|null, // Deprecated: The "workbox_public_url" option is deprecated and will be removed in 2.0.0. use "config.workbox_public_url" instead. // The public path to the local workbox. Only used if use_cdn is false. // Default: "/workbox" + * idb_public_url?: scalar|Param|null, // The public path to the local IndexDB. Only used if use_cdn is false. // Default: "/idb" + * workbox_import_placeholder?: scalar|Param|null, // Deprecated: The "workbox_import_placeholder" option is deprecated and will be removed in 2.0.0. No replacement. // The placeholder for the workbox import. Will be replaced by the workbox import. // Default: "//WORKBOX_IMPORT_PLACEHOLDER" + * standard_rules_placeholder?: scalar|Param|null, // Deprecated: The "standard_rules_placeholder" option is deprecated and will be removed in 2.0.0. No replacement. // The placeholder for the standard rules. Will be replaced by caching strategies. // Default: "//STANDARD_RULES_PLACEHOLDER" + * offline_fallback_placeholder?: scalar|Param|null, // Deprecated: The "offline_fallback_placeholder" option is deprecated and will be removed in 2.0.0. No replacement. // The placeholder for the offline fallback. Will be replaced by the URL. // Default: "//OFFLINE_FALLBACK_PLACEHOLDER" + * widgets_placeholder?: scalar|Param|null, // Deprecated: The "widgets_placeholder" option is deprecated and will be removed in 2.0.0. No replacement. // The placeholder for the widgets. Will be replaced by the widgets management events. // Default: "//WIDGETS_PLACEHOLDER" + * clear_cache?: bool|Param, // Whether to clear the cache during the service worker activation. // Default: true + * navigation_preload?: bool|Param, // Whether to enable navigation preload. This speeds up navigation requests by making the network request in parallel with service worker boot-up. Note: Do not enable if you are precaching HTML pages (e.g., with offline_fallback or warm_cache_urls), as it would be redundant. // Default: false + * config?: array{ + * debug?: bool|Param, // Controls workbox debug logging. Set to false to disable debug mode and logging. // Default: true + * version?: scalar|Param|null, // The version of workbox. When using local files, the version shall be "7.0.0." // Default: "7.3.0" + * use_cdn?: bool|Param, // Whether to use the local workbox or the CDN. // Default: false + * workbox_public_url?: scalar|Param|null, // The public path to the local workbox. Only used if use_cdn is false. // Default: "/workbox" + * }, + * offline_fallback?: array{ + * cache_name?: scalar|Param|null, // The name of the offline cache. // Default: "offline" + * page?: string|array{ // The URL of the offline page fallback. + * path?: scalar|Param|null, // The URL or route name. + * path_type_reference?: int|Param, // The path type reference to generate paths/URLs. See https://symfony.com/doc/current/routing.html#generating-urls-in-controllers for more information. // Default: 1 + * params?: list, + * }, + * image?: string|array{ // The URL of the offline image fallback. + * path?: scalar|Param|null, // The URL or route name. + * path_type_reference?: int|Param, // The path type reference to generate paths/URLs. See https://symfony.com/doc/current/routing.html#generating-urls-in-controllers for more information. // Default: 1 + * params?: list, + * }, + * font?: string|array{ // The URL of the offline font fallback. + * path?: scalar|Param|null, // The URL or route name. + * path_type_reference?: int|Param, // The path type reference to generate paths/URLs. See https://symfony.com/doc/current/routing.html#generating-urls-in-controllers for more information. // Default: 1 + * params?: list, + * }, + * }, + * image_cache?: bool|array{ + * enabled?: bool|Param, // Default: true + * cache_name?: scalar|Param|null, // The name of the image cache. // Default: "images" + * regex?: scalar|Param|null, // The regex to match the images. // Default: "/\\.(ico|png|jpe?g|gif|svg|webp|bmp)$/" + * max_entries?: int|Param, // The maximum number of entries in the image cache. // Default: 60 + * max_age?: scalar|Param|null, // The maximum number of seconds before the image cache is invalidated. // Default: 31536000 + * }, + * asset_cache?: bool|array{ + * enabled?: bool|Param, // Default: true + * cache_name?: scalar|Param|null, // The name of the asset cache. // Default: "assets" + * regex?: scalar|Param|null, // The regex to match the assets. // Default: "/\\.(css|js|json|xml|txt|map|ico|png|jpe?g|gif|svg|webp|bmp)$/" + * max_age?: scalar|Param|null, // The maximum number of seconds before the asset cache is invalidated. // Default: 31536000 + * }, + * font_cache?: bool|array{ + * enabled?: bool|Param, // Default: true + * cache_name?: scalar|Param|null, // The name of the font cache. // Default: "fonts" + * regex?: scalar|Param|null, // The regex to match the fonts. // Default: "/\\.(ttf|eot|otf|woff2)$/" + * max_entries?: int|Param, // The maximum number of entries in the image cache. // Default: 60 + * max_age?: int|Param, // The maximum number of seconds before the font cache is invalidated. // Default: 31536000 + * }, + * resource_caches?: list, + * cacheable_response_statuses?: list, + * broadcast_headers?: list, + * preload_urls?: list, + * }>, + * }>, + * background_sync?: list, + * expect_redirect?: bool|Param, // Whether to expect a redirect (JS response type should be "opaqueredirect" or the "redirected" property is "true"). // Default: false + * method?: scalar|Param|null, // The HTTP method. // Default: "POST" + * broadcast_channel?: scalar|Param|null, // The broadcast channel. Set null to disable. // Default: null + * max_retention_time?: int|Param, // The maximum retention time in minutes. // Default: 1440 + * force_sync_fallback?: bool|Param, // If `true`, instead of attempting to use background sync events, always attempt to replay queued request at service worker startup. Most folks will not need this, unless you explicitly target a runtime like Electron that exposes the interfaces for background sync, but does not have a working implementation. // Default: false + * }>, + * background_fetch?: bool|array{ + * enabled?: bool|Param, // Default: false + * db_name?: scalar|Param|null, // The IndexDB name where downloads are stored // Default: "bgfetch-completed" + * progress_url?: string|array{ // The URL of the progress page. + * path?: scalar|Param|null, // The URL or route name. + * path_type_reference?: int|Param, // The path type reference to generate paths/URLs. See https://symfony.com/doc/current/routing.html#generating-urls-in-controllers for more information. // Default: 1 + * params?: list, + * }, + * success_url?: string|array{ // The URL of the success page. + * path?: scalar|Param|null, // The URL or route name. + * path_type_reference?: int|Param, // The path type reference to generate paths/URLs. See https://symfony.com/doc/current/routing.html#generating-urls-in-controllers for more information. // Default: 1 + * params?: list, + * }, + * success_message?: scalar|Param|null, // The message to display on success. This message is translated. // Default: null + * failure_message?: scalar|Param|null, // The message to display on success. This message is translated. // Default: null + * }, + * image_cache_name?: scalar|Param|null, // Deprecated: The "image_cache_name" option is deprecated and will be removed in 2.0.0. Please use "pwa.serviceworker.workbox.image_cache.cache_name" instead. // The name of the image cache. // Default: "images" + * font_cache_name?: scalar|Param|null, // Deprecated: The "font_cache_name" option is deprecated and will be removed in 2.0.0. Please use "pwa.serviceworker.workbox.font_cache.cache_name" instead. // The name of the font cache. // Default: "fonts" + * page_cache_name?: scalar|Param|null, // Deprecated: The "page_cache_name" option is deprecated and will be removed in 2.0.0. Please use "pwa.serviceworker.workbox.resource_caches[].cache_name" instead. // The name of the page cache. // Default: "pages" + * asset_cache_name?: scalar|Param|null, // Deprecated: The "asset_cache_name" option is deprecated and will be removed in 2.0.0. Please use "pwa.serviceworker.workbox.asset_cache.cache_name" instead. // The name of the asset cache. // Default: "assets" + * page_fallback?: string|array{ // The URL of the offline page fallback. + * path?: scalar|Param|null, // The URL or route name. + * path_type_reference?: int|Param, // The path type reference to generate paths/URLs. See https://symfony.com/doc/current/routing.html#generating-urls-in-controllers for more information. // Default: 1 + * params?: list, + * }, + * image_fallback?: string|array{ // The URL of the offline image fallback. + * path?: scalar|Param|null, // The URL or route name. + * path_type_reference?: int|Param, // The path type reference to generate paths/URLs. See https://symfony.com/doc/current/routing.html#generating-urls-in-controllers for more information. // Default: 1 + * params?: list, + * }, + * font_fallback?: string|array{ // The URL of the offline font fallback. + * path?: scalar|Param|null, // The URL or route name. + * path_type_reference?: int|Param, // The path type reference to generate paths/URLs. See https://symfony.com/doc/current/routing.html#generating-urls-in-controllers for more information. // Default: 1 + * params?: list, + * }, + * image_regex?: scalar|Param|null, // Deprecated: The "image_regex" option is deprecated and will be removed in 2.0.0. Please use "pwa.serviceworker.workbox.image_cache.regex" instead. // The regex to match the images. // Default: "/\\.(ico|png|jpe?g|gif|svg|webp|bmp)$/" + * static_regex?: scalar|Param|null, // Deprecated: The "static_regex" option is deprecated and will be removed in 2.0.0. Please use "pwa.serviceworker.workbox.asset_cache.regex" instead. // The regex to match the static files. // Default: "/\\.(css|js|json|xml|txt|map)$/" + * font_regex?: scalar|Param|null, // Deprecated: The "font_regex" option is deprecated and will be removed in 2.0.0. Please use "pwa.serviceworker.workbox.font_cache.regex" instead. // The regex to match the static files. // Default: "/\\.(ttf|eot|otf|woff2)$/" + * max_image_cache_entries?: int|Param, // Deprecated: The "max_image_cache_entries" option is deprecated and will be removed in 2.0.0. Please use "pwa.serviceworker.workbox.image_cache.max_entries" instead. // The maximum number of entries in the image cache. // Default: 60 + * max_image_age?: int|Param, // Deprecated: The "max_image_age" option is deprecated and will be removed in 2.0.0. Please use "pwa.serviceworker.workbox.image_cache.max_age" instead. // The maximum number of seconds before the image cache is invalidated. // Default: 31536000 + * max_font_cache_entries?: int|Param, // Deprecated: The "max_font_cache_entries" option is deprecated and will be removed in 2.0.0. Please use "pwa.serviceworker.workbox.font_cache.max_entries" instead. // The maximum number of entries in the font cache. // Default: 30 + * max_font_age?: int|Param, // Deprecated: The "max_font_age" option is deprecated and will be removed in 2.0.0. Please use "pwa.serviceworker.workbox.font_cache.max_age" instead. // The maximum number of seconds before the font cache is invalidated. // Default: 31536000 + * network_timeout_seconds?: int|Param, // Deprecated: The "network_timeout_seconds" option is deprecated and will be removed in 2.0.0. Please use "pwa.serviceworker.workbox.resource_caches[].network_timeout" instead. // The network timeout in seconds before cache is called (for warm cache URLs only). // Default: 3 + * warm_cache_urls?: list, + * }>, + * }, + * }, + * speculation_rules?: bool|array{ // Speculation Rules API configuration for prefetching and prerendering pages. + * enabled?: bool|Param, // Default: false + * prefetch?: list, + * }>, + * selector_matches?: scalar|Param|null, // For "document" source: CSS selector to match links. // Default: null + * href_matches?: scalar|Param|null, // For "document" source: URL pattern to match href attributes. // Default: null + * eagerness?: "immediate"|"eager"|"moderate"|"conservative"|Param, // Eagerness level: "immediate" (viewport), "eager" (hover 200ms), "moderate" (hover 100ms), "conservative" (mousedown/touchstart). // Default: "moderate" + * referrer_policy?: scalar|Param|null, // Referrer policy for the speculative request. // Default: null + * }>, + * prerender?: list, + * }>, + * selector_matches?: scalar|Param|null, // For "document" source: CSS selector to match links. // Default: null + * href_matches?: scalar|Param|null, // For "document" source: URL pattern to match href attributes. // Default: null + * eagerness?: "immediate"|"eager"|"moderate"|"conservative"|Param, // Eagerness level. For prerender, "conservative" is recommended. // Default: "conservative" + * referrer_policy?: scalar|Param|null, // Referrer policy for the speculative request. // Default: null + * }>, + * }, + * web_client?: scalar|Param|null, // The Panther Client for generating screenshots. If not set, the default client will be used. // Default: null + * user_agent?: scalar|Param|null, // The user agent to use when generating screenshots. When this user agent is detected, the Symfony profiler and debug toolbar will be automatically disabled to ensure screenshots look like production. // Default: "PWAScreenshotBot" + * } + * @psalm-type VichUploaderConfig = array{ + * default_filename_attribute_suffix?: scalar|Param|null, // Default: "_name" + * db_driver?: scalar|Param|null, + * storage?: scalar|Param|null, // Default: "file_system" + * use_flysystem_to_resolve_uri?: bool|Param, // Default: false + * twig?: scalar|Param|null, // twig requires templating // Default: true + * form?: scalar|Param|null, // Default: true + * metadata?: array{ + * cache?: scalar|Param|null, // Default: "file" + * type?: scalar|Param|null, // Default: "attribute" + * file_cache?: array{ + * dir?: scalar|Param|null, // Default: "%kernel.cache_dir%/vich_uploader" + * }, + * auto_detection?: bool|Param, // Default: true + * directories?: list, + * }, + * mappings?: array, + * } + * @psalm-type SchebTwoFactorConfig = array{ + * persister?: scalar|Param|null, // Default: "scheb_two_factor.persister.doctrine" + * model_manager_name?: scalar|Param|null, // Default: null + * security_tokens?: list, + * ip_whitelist?: list, + * ip_whitelist_provider?: scalar|Param|null, // Default: "scheb_two_factor.default_ip_whitelist_provider" + * two_factor_token_factory?: scalar|Param|null, // Default: "scheb_two_factor.default_token_factory" + * two_factor_provider_decider?: scalar|Param|null, // Default: "scheb_two_factor.default_provider_decider" + * two_factor_condition?: scalar|Param|null, // Default: null + * code_reuse_cache?: scalar|Param|null, // Default: null + * code_reuse_cache_duration?: int|Param, // Default: 60 + * code_reuse_default_handler?: scalar|Param|null, // Default: null + * backup_codes?: bool|array{ + * enabled?: scalar|Param|null, // Default: false + * manager?: scalar|Param|null, // Default: "scheb_two_factor.default_backup_code_manager" + * }, + * email?: bool|array{ + * enabled?: scalar|Param|null, // Default: false + * mailer?: scalar|Param|null, // Default: null + * code_generator?: scalar|Param|null, // Default: "scheb_two_factor.security.email.default_code_generator" + * form_renderer?: scalar|Param|null, // Default: null + * sender_email?: scalar|Param|null, // Default: null + * sender_name?: scalar|Param|null, // Default: null + * template?: scalar|Param|null, // Default: "@SchebTwoFactor/Authentication/form.html.twig" + * digits?: int|Param, // Default: 4 + * }, + * google?: bool|array{ + * enabled?: scalar|Param|null, // Default: false + * form_renderer?: scalar|Param|null, // Default: null + * issuer?: scalar|Param|null, // Default: null + * server_name?: scalar|Param|null, // Default: null + * template?: scalar|Param|null, // Default: "@SchebTwoFactor/Authentication/form.html.twig" + * digits?: int|Param, // Default: 6 + * leeway?: int|Param, // Default: 0 + * }, + * } * @psalm-type ConfigType = array{ * imports?: ImportsConfig, * parameters?: ParametersConfig, @@ -1463,11 +2283,18 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param; * doctrine?: DoctrineConfig, * doctrine_migrations?: DoctrineMigrationsConfig, * twig?: TwigConfig, - * stimulus?: StimulusConfig, - * turbo?: TurboConfig, * twig_extra?: TwigExtraConfig, * security?: SecurityConfig, * monolog?: MonologConfig, + * endroid_qr_code?: EndroidQrCodeConfig, + * knp_paginator?: KnpPaginatorConfig, + * knpu_oauth2_client?: KnpuOauth2ClientConfig, + * flysystem?: FlysystemConfig, + * liip_imagine?: LiipImagineConfig, + * nelmio_security?: NelmioSecurityConfig, + * pwa?: PwaConfig, + * vich_uploader?: VichUploaderConfig, + * scheb_two_factor?: SchebTwoFactorConfig, * "when@dev"?: array{ * imports?: ImportsConfig, * parameters?: ParametersConfig, @@ -1478,12 +2305,19 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param; * debug?: DebugConfig, * twig?: TwigConfig, * web_profiler?: WebProfilerConfig, - * stimulus?: StimulusConfig, - * turbo?: TurboConfig, * twig_extra?: TwigExtraConfig, * security?: SecurityConfig, * monolog?: MonologConfig, * maker?: MakerConfig, + * endroid_qr_code?: EndroidQrCodeConfig, + * knp_paginator?: KnpPaginatorConfig, + * knpu_oauth2_client?: KnpuOauth2ClientConfig, + * flysystem?: FlysystemConfig, + * liip_imagine?: LiipImagineConfig, + * nelmio_security?: NelmioSecurityConfig, + * pwa?: PwaConfig, + * vich_uploader?: VichUploaderConfig, + * scheb_two_factor?: SchebTwoFactorConfig, * }, * "when@prod"?: array{ * imports?: ImportsConfig, @@ -1493,11 +2327,18 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param; * doctrine?: DoctrineConfig, * doctrine_migrations?: DoctrineMigrationsConfig, * twig?: TwigConfig, - * stimulus?: StimulusConfig, - * turbo?: TurboConfig, * twig_extra?: TwigExtraConfig, * security?: SecurityConfig, * monolog?: MonologConfig, + * endroid_qr_code?: EndroidQrCodeConfig, + * knp_paginator?: KnpPaginatorConfig, + * knpu_oauth2_client?: KnpuOauth2ClientConfig, + * flysystem?: FlysystemConfig, + * liip_imagine?: LiipImagineConfig, + * nelmio_security?: NelmioSecurityConfig, + * pwa?: PwaConfig, + * vich_uploader?: VichUploaderConfig, + * scheb_two_factor?: SchebTwoFactorConfig, * }, * "when@test"?: array{ * imports?: ImportsConfig, @@ -1508,11 +2349,18 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param; * doctrine_migrations?: DoctrineMigrationsConfig, * twig?: TwigConfig, * web_profiler?: WebProfilerConfig, - * stimulus?: StimulusConfig, - * turbo?: TurboConfig, * twig_extra?: TwigExtraConfig, * security?: SecurityConfig, * monolog?: MonologConfig, + * endroid_qr_code?: EndroidQrCodeConfig, + * knp_paginator?: KnpPaginatorConfig, + * knpu_oauth2_client?: KnpuOauth2ClientConfig, + * flysystem?: FlysystemConfig, + * liip_imagine?: LiipImagineConfig, + * nelmio_security?: NelmioSecurityConfig, + * pwa?: PwaConfig, + * vich_uploader?: VichUploaderConfig, + * scheb_two_factor?: SchebTwoFactorConfig, * }, * ...> /proc/1/fd/1 && php /app/bin/console app:orders:expire-pending --env=dev >> /proc/1/fd/1 2>&1 && echo "[$(date '+\%Y-\%m-\%d \%H:\%M:\%S')] END app:orders:expire-pending" >> /proc/1/fd/1 +0 * * * * echo "[$(date '+\%Y-\%m-\%d \%H:\%M:\%S')] START app:monitor:messenger" >> /proc/1/fd/1 && php /app/bin/console app:monitor:messenger --env=dev >> /proc/1/fd/1 2>&1 && echo "[$(date '+\%Y-\%m-\%d \%H:\%M:\%S')] END app:monitor:messenger" >> /proc/1/fd/1 +0 3 * * * echo "[$(date '+\%Y-\%m-\%d \%H:\%M:\%S')] START app:meilisearch:check-consistency" >> /proc/1/fd/1 && php /app/bin/console app:meilisearch:check-consistency --fix --env=dev >> /proc/1/fd/1 2>&1 && echo "[$(date '+\%Y-\%m-\%d \%H:\%M:\%S')] END app:meilisearch:check-consistency" >> /proc/1/fd/1 +0 */6 * * * echo "[$(date '+\%Y-\%m-\%d \%H:\%M:\%S')] START app:stripe:sync" >> /proc/1/fd/1 && php /app/bin/console app:stripe:sync --env=dev >> /proc/1/fd/1 2>&1 && echo "[$(date '+\%Y-\%m-\%d \%H:\%M:\%S')] END app:stripe:sync" >> /proc/1/fd/1 +*/5 * * * * php /app/bin/console app:infra:snapshot --env=dev >> /proc/1/fd/1 2>&1 +0 4 * * * echo "[$(date '+\%Y-\%m-\%d \%H:\%M:\%S')] START app:attestations:clean" >> /proc/1/fd/1 && php /app/bin/console app:attestations:clean --env=dev >> /proc/1/fd/1 2>&1 && echo "[$(date '+\%Y-\%m-\%d \%H:\%M:\%S')] END app:attestations:clean" >> /proc/1/fd/1 +*/15 * * * * echo "[$(date '+\%Y-\%m-\%d \%H:\%M:\%S')] START app:services:check" >> /proc/1/fd/1 && php /app/bin/console app:services:check --env=dev >> /proc/1/fd/1 2>&1 && echo "[$(date '+\%Y-\%m-\%d \%H:\%M:\%S')] END app:services:check" >> /proc/1/fd/1 diff --git a/docker/cron/entrypoint.sh b/docker/cron/entrypoint.sh new file mode 100644 index 0000000..edd5349 --- /dev/null +++ b/docker/cron/entrypoint.sh @@ -0,0 +1,10 @@ +#!/bin/sh +echo "=== CRM Ecosplay Cron ===" +echo "Registered tasks:" +echo " - */5 * * * * app:orders:expire-pending" +echo " - 0 * * * * app:monitor:messenger" +echo " - 0 3 * * * app:meilisearch:check-consistency --fix" +echo " - 0 4 * * * app:attestations:clean" +echo " - */15 * * * app:services:check" +echo "====================" +exec cron -f diff --git a/docker/ngrok/sync.sh b/docker/ngrok/sync.sh new file mode 100755 index 0000000..0378a40 --- /dev/null +++ b/docker/ngrok/sync.sh @@ -0,0 +1,29 @@ +#!/bin/sh +set -e + +echo "Waiting for ngrok to start..." +sleep 5 + +NGROK_URL="" +RETRIES=10 + +while [ -z "$NGROK_URL" ] && [ "$RETRIES" -gt 0 ]; do + NGROK_URL=$(curl -s http://ngrok:4040/api/tunnels | grep -o '"public_url":"https://[^"]*"' | head -1 | cut -d'"' -f4) + if [ -z "$NGROK_URL" ]; then + echo "Waiting for tunnel..." + sleep 2 + RETRIES=$((RETRIES - 1)) + fi +done + +if [ -z "$NGROK_URL" ]; then + echo "ERROR: Could not get ngrok URL" + exit 1 +fi + +touch /app/.env.local +sed -i '/^OUTSIDE_URL=/d' /app/.env.local +echo "OUTSIDE_URL=$NGROK_URL" >> /app/.env.local + +echo "Ngrok URL: $NGROK_URL" +echo "Written to .env.local" diff --git a/docker/pgsql/init-master.sh b/docker/pgsql/init-master.sh new file mode 100755 index 0000000..fb0e5f5 --- /dev/null +++ b/docker/pgsql/init-master.sh @@ -0,0 +1,12 @@ +#!/bin/bash +set -e + +psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL + CREATE USER replicator WITH REPLICATION ENCRYPTED PASSWORD 'crm-ecosplay'; + SELECT pg_create_physical_replication_slot('slave_slot'); +EOSQL + +echo "host replication replicator 0.0.0.0/0 md5" >> "$PGDATA/pg_hba.conf" +echo "host all all 0.0.0.0/0 md5" >> "$PGDATA/pg_hba.conf" + +pg_ctl reload -D "$PGDATA" diff --git a/docker/pgsql/init-master.sql b/docker/pgsql/init-master.sql new file mode 100644 index 0000000..76842fe --- /dev/null +++ b/docker/pgsql/init-master.sql @@ -0,0 +1,2 @@ +CREATE USER replicator WITH REPLICATION ENCRYPTED PASSWORD 'crm-ecosplay'; +SELECT pg_create_physical_replication_slot('slave_slot'); diff --git a/docker/pgsql/init-slave.sh b/docker/pgsql/init-slave.sh new file mode 100755 index 0000000..9d25853 --- /dev/null +++ b/docker/pgsql/init-slave.sh @@ -0,0 +1,20 @@ +#!/bin/bash +set -e + +until pg_isready -h db-master -U crm-ecosplay; do + echo "Waiting for master..." + sleep 2 +done + +echo "db-master:5432:replication:replicator:crm-ecosplay" > /tmp/.pgpass +chown postgres:postgres /tmp/.pgpass +chmod 600 /tmp/.pgpass + +rm -rf "$PGDATA"/* +chown postgres:postgres "$PGDATA" +chmod 700 "$PGDATA" +su-exec postgres env PGPASSFILE=/tmp/.pgpass pg_basebackup -h db-master -D "$PGDATA" -U replicator -Fp -Xs -P -R + +echo "hot_standby = on" >> "$PGDATA/postgresql.conf" + +exec su-exec postgres "$@" diff --git a/docker/pgsql/pgbouncer-dev.ini b/docker/pgsql/pgbouncer-dev.ini new file mode 100644 index 0000000..db9621a --- /dev/null +++ b/docker/pgsql/pgbouncer-dev.ini @@ -0,0 +1,22 @@ +[databases] +crm_ecosplay = host=database port=5432 dbname=crm_ecosplay +crm_ecosplay_test = host=database port=5432 dbname=crm_ecosplay_test + +[pgbouncer] +listen_addr = 0.0.0.0 +listen_port = 6432 +auth_type = plain +auth_file = /etc/pgbouncer/userlist.txt +pool_mode = transaction +max_client_conn = 200 +default_pool_size = 20 +min_pool_size = 5 +reserve_pool_size = 5 +reserve_pool_timeout = 3 +server_lifetime = 3600 +server_idle_timeout = 600 +log_connections = 0 +log_disconnections = 0 +ignore_startup_parameters = extra_float_digits +admin_users = app +stats_users = app diff --git a/docker/pgsql/userlist-dev.txt b/docker/pgsql/userlist-dev.txt new file mode 100644 index 0000000..2153323 --- /dev/null +++ b/docker/pgsql/userlist-dev.txt @@ -0,0 +1 @@ +"app" "secret" diff --git a/docker/php/dev/Dockerfile b/docker/php/dev/Dockerfile new file mode 100644 index 0000000..e574dc1 --- /dev/null +++ b/docker/php/dev/Dockerfile @@ -0,0 +1,35 @@ +# hadolint global ignore=DL3008 +FROM php:8.4-fpm + +RUN apt-get update && apt-get install -y --no-install-recommends \ + libpq-dev \ + libsqlite3-dev \ + libzip-dev \ + libxml2-dev \ + libicu-dev \ + libpng-dev \ + libjpeg-dev \ + libfreetype-dev \ + libmagickwand-dev \ + unzip \ + qpdf \ + curl \ + python3 \ + python3-pip \ + git \ + && rm -rf /var/lib/apt/lists/* \ + && docker-php-ext-configure gd --with-freetype --with-jpeg \ + && docker-php-ext-install \ + pdo_mysql \ + pdo_pgsql \ + pdo_sqlite \ + zip \ + intl \ + gd \ + && pecl install redis imagick pcov \ + && docker-php-ext-enable redis imagick pcov \ + && groupadd -g 1000 appuser && useradd -u 1000 -g appuser -m appuser + +WORKDIR /app + +USER appuser diff --git a/docker/php/prod/Dockerfile b/docker/php/prod/Dockerfile new file mode 100644 index 0000000..8e891c3 --- /dev/null +++ b/docker/php/prod/Dockerfile @@ -0,0 +1,35 @@ +# hadolint global ignore=DL3008 +FROM php:8.4-fpm + +RUN apt-get update && apt-get install -y --no-install-recommends \ + libpq-dev \ + libsqlite3-dev \ + libzip-dev \ + libxml2-dev \ + libicu-dev \ + libpng-dev \ + libjpeg-dev \ + libfreetype-dev \ + libmagickwand-dev \ + unzip \ + && rm -rf /var/lib/apt/lists/* \ + && docker-php-ext-configure gd --with-freetype --with-jpeg \ + && docker-php-ext-install \ + pdo_mysql \ + pdo_pgsql \ + pdo_sqlite \ + zip \ + intl \ + gd \ + exif \ + opcache \ + && pecl install redis imagick \ + && docker-php-ext-enable redis imagick \ + && groupadd -g 1000 appuser && useradd -u 1000 -g appuser -m appuser + +COPY php.ini /usr/local/etc/php/conf.d/app.ini +COPY opcache.ini /usr/local/etc/php/conf.d/opcache.ini + +WORKDIR /app + +USER appuser diff --git a/docker/php/prod/opcache.ini b/docker/php/prod/opcache.ini new file mode 100644 index 0000000..0f7044e --- /dev/null +++ b/docker/php/prod/opcache.ini @@ -0,0 +1,9 @@ +opcache.enable=1 +opcache.memory_consumption=256 +opcache.interned_strings_buffer=16 +opcache.max_accelerated_files=20000 +opcache.validate_timestamps=0 +opcache.save_comments=0 +opcache.enable_cli=1 +opcache.jit=tracing +opcache.jit_buffer_size=128M diff --git a/docker/php/prod/php.ini b/docker/php/prod/php.ini new file mode 100644 index 0000000..6f3d3a7 --- /dev/null +++ b/docker/php/prod/php.ini @@ -0,0 +1,21 @@ +date.timezone = Europe/Paris + +memory_limit = 256M +upload_max_filesize = 100M +post_max_size = 150M +max_execution_time = 30 +max_input_time = 30 + +expose_php = Off +display_errors = Off +display_startup_errors = Off +log_errors = On +error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT + +realpath_cache_size = 4096K +realpath_cache_ttl = 600 + +session.cookie_secure = On +session.cookie_httponly = On +session.cookie_samesite = Lax +session.use_strict_mode = 1 diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..4bf33ad --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,36 @@ +export default [ + { + files: ["assets/**/*.js"], + languageOptions: { + globals: { + document: "readonly", + window: "readonly", + console: "readonly", + setTimeout: "readonly", + globalThis: "readonly", + navigator: "readonly", + fetch: "readonly", + caches: "readonly", + Request: "readonly", + Response: "readonly", + BroadcastChannel: "readonly", + DOMParser: "readonly", + setInterval: "readonly", + clearInterval: "readonly", + URLSearchParams: "readonly", + FormData: "readonly", + }, + }, + rules: { + "no-unused-vars": "warn", + "no-undef": "error", + "no-console": "warn", + "eqeqeq": "error", + "no-var": "error", + "prefer-const": "warn", + }, + }, + { + ignores: ["node_modules/", "public/build/", "vendor/", "public/sw.js", "public/workbox/", "public/idb/", "assets/vendor/"], + }, +]; diff --git a/importmap.php b/importmap.php index b73b323..16c52e1 100644 --- a/importmap.php +++ b/importmap.php @@ -19,10 +19,13 @@ return [ '@hotwired/stimulus' => [ 'version' => '3.2.2', ], - '@symfony/stimulus-bundle' => [ - 'path' => './vendor/symfony/stimulus-bundle/assets/dist/loader.js', + 'idb' => [ + 'version' => '8.0.3', ], - '@hotwired/turbo' => [ - 'version' => '7.3.0', + 'idb-keyval' => [ + 'version' => '6.2.2', + ], + '@spomky-labs/pwa/helpers' => [ + 'path' => './vendor/spomky-labs/pwa-bundle/assets/src/helpers.js', ], ]; diff --git a/key.asc b/key.asc new file mode 100644 index 0000000..25e23f3 --- /dev/null +++ b/key.asc @@ -0,0 +1,51 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +xsFNBGl/HrcBEACarK9KzE+J9MhuojbJyR4wU65Cf1fJEbS9NfhbHplaaELDv8+e ++aeCliEvgZI25UFnHzhvrSeQD1AKkhbWQRxmUDFCDzylhNSwpbdrdckvGEaV90dN +b98a7hFjk42b+IuDpxESlqIoB5+sq9/iQhT9pUQcAwVPF/vgZmaT3dKlCRJQYXaA +pqBGERweEvI5P+zZgy2uNjAwkNzBSORY9M5K9SeiDkRb27MJKh98CTykSIYc+Qwa +IGsFyX/7ZqmnfR5xTzdN1Q/jgUKS2gvdXxzS6qH1mKbngH9M4mMrT4QdmDDLYLIY +jAzSmUjXWBEYvAgEIJ2LSSyvGkuOFfnQ1iRn78ahYClI62SIJXoVIvG9L+3Yxi+6 +c/Yd3ILx0b+m7PNoJsCmh6S/oBMBzKJpW2N2SEGpOhhN6wisfBuLwliNUFji9B6a +CZ375ebj032B7FhWbgPg0IKj0T9sJLq+grRubxF8KvEk8HwOkJ+D8ocbPitFV1wM +Vq+2IyM1V0Cqp4eUBpsumKhqrejoIaCWrXiaSRFJBi2bWbGG5np152RAZsEntXHX +gyA+zWLYiCVcyv+Lshqao9NEw+V2S1m+wkMcYB5EyzoyEkTeQMZduF3DmjZIQJ1N +k6X9G3hiqTY89PjmwZ1h8O5allXAyRNgmi+UmjZ3L5EI/hqdK0MKFA0oxQARAQAB +zSBFLUNvc3BsYXkgPGNvbnRhY3RAZS1jb3NwbGF5LmZyPsLBhwQTAQgAMRYhBJBl +Q9S6KNCt9/BrsTk4SZ4B7ZAkBQJpfx64AhsDBAsJCAcFFQgJCgsFFgIDAQAACgkQ +OThJngHtkCR14Q//R3gXwwl9KVtnta+JSLP72BhjSXSEXYj3moTUK6ZXANqCfOJ/ +LI1loN+pH/+8WaBRpVv4Z7fLoqWfvjkj0sFUVT6bvPcbZMYxtWTgT/sTEiM1maup +9ChTgE7/TSWbC2PckyishUjrqSHGpg9RTwh8P+W+0TWzCEAlrGMwgidFfRaVY44V +20Id/guAxJy2w+BhUlZ0YRDKZSrI26ezJQEskfPi07IzGZTM3mgx77U2gYj+P6ig +zmYzyH1ISdTDTNgs+bulqfoKgWC2uPDKTYmhUckq8bhaLyPcGEspwcMVunY6pCu/ +U69zvZZVshIk0Tn2Z+pGr47N5c8R6FAi5nDuzrxy2kJaH6uV8AsfkYlzJm033hJB +H3XqaJYMAr0Xm30RhecuOAFKdxnPLSxTNhllovULaL6IvXnBY4+/PD9yFjaqrJQW +omTxNQvL6vwLm15KDP93Toukeu249ZLDMU74VTK+4O9wxdy54SX6u1m+xloVAisU ++KDyjEDqeOZ5ofkVn4OH6EXVeGyIRra6q/0xI5NZlPaBao3jGyEz1XbExG6fn3QC +c/nA/QBCaPSB/xfVTo0hNM2qiv32+y+x3eCEckFjYNBDtqFZlt3Lh0H3LbTALLSK +I9k82fg9DWQIYts43e3LjAIh/5aV8Xg3F2Bn/o+vOSbFygQbPX3dE3NlG4LOwU0E +aX8euAEQANIpA/G/fdDXcvYbjyzCDFXkA1MUv+GbU+4u7giA59/Mkajxr6o3qEXj +IfsQQTCNe14B/D8yRtt4fCL+Zj7O01T730RpQuUiF+aV0S0I80QS+X2vZN3SdBXV +TOKRYKXk8lqt0U1FWVAhyHR8cZ4bAp1sp50Klc8mln8G96CTmQ+ffV8QQq85jFWU +zsDBEG0DxNhNigt+EOBOEbAOGvJlXjVFHZ1pIFTbTUDM3ExS1IFJoze2c0cu/2at +KZNnsRXxaGwhCFZLUYRbR2m+XQyYnIcSIPekq0H091qXjcxmrsCtaF0SlCowF3gc +765h0PuPdTXc+2rRuUNnNwPugqs/qWmbjcKCyJF5Y1yqMbAwxzafskxNAooBkw4Q +huGlIhRGNYauhwDWxWSev7sdZiAfyfn8nh4GgfF0T6Vx5ycRnp9ee8Bc5dM72T+q +kF5Qnu38lNLsJbxzSi2jyiyeuJGpQRAItWSXUax1bzCjxsl9BBefuAdns+yc1jgk +h2a4YC1Um/I6lDdVrDRiOCQ9In0LCesKL1plj/D8qG5CTl4YjzNHwlPIoMPiNgiJ +vnQMvcdcYWvf4a0XvTWf3QbzalNYH0mvMWrTkeby7o6/Y7rmnPQmNYPGYz6v+ayg +GwMi3MwtD/mVr2PCD8ICgXH5r9fySGpW5YAK50t7PYyfu621JiNxABEBAAHCwXYE +GAEIACAWIQSQZUPUuijQrffwa7E5OEmeAe2QJAUCaX8euAIbDAAKCRA5OEmeAe2Q +JMIvD/9NFCQA3jMpZ2WUhLtqf7XOHsu5ncuIDRg9VViBV73GsbfsJGnKekploLPw +j8JZ0SVpYLTMmQN2Cki8ErZYmHyAn65xGPjT/2uN1CWnFWq44fhiJoJu3mkwYKzD +EAwviAVyMU7zSWhjP1qg2aqVe4c4Nb52tEdruiLfYSeSDbXwyzQqarmjwFhbwK4/ +YmYCq5WlRriD9AIfoef+sYfuKPNkegJP2JqekQ0vZ3EjXmBghwH4YG4HVKEHLT7n ++8UcOLMln+lPFP+Ea++r8wmQbGEGSG/7V8GhIlta0kYg3iEZpLb9K0XZJ0PWYq5H +uFkjePnCtROOhqQjIuXN9wdejICksNURbssjuIbWQ/mMx8xAoQcMZjr93z1icBsx +nxLqlZhq1abnXVKkU7Eu+ynULGXNqfcF2Q6ApVSkp8uE5yOed3o9yRNb6zzyhLqB +64NS74As8cLp2NwYIvwbAZz6EyGad/5L2hW5unbyXGWL/EKx2wQk6E9o9Jn35OOt +E5zXzEsYfCTIp4Gt65kHl0U/E3pdvJhu71vuOlMQZzlP5i1WcZXOFbiKdtW+wf6K +GCDvkAOtrItxaqTUCMD9lknJ+uVlvLtWvFWZxTUx1w7lLzPunA6usrayf77WROca +BzdSeoY1RgWbeo/TziGUcW4KMiso/j13uOweG4NeNkrW1MAHsg== +=5F8Q +-----END PGP PUBLIC KEY BLOCK----- diff --git a/migrations/Version20260330204131.php b/migrations/Version20260330204131.php new file mode 100644 index 0000000..951f9b4 --- /dev/null +++ b/migrations/Version20260330204131.php @@ -0,0 +1,57 @@ +addSql('CREATE TABLE analytics_event (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, event_name VARCHAR(50) NOT NULL, url VARCHAR(2048) NOT NULL, title VARCHAR(255) DEFAULT NULL, referrer VARCHAR(2048) DEFAULT NULL, created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, visitor_id INT NOT NULL, PRIMARY KEY (id))'); + $this->addSql('CREATE INDEX IDX_9CD0310A70BEE6D ON analytics_event (visitor_id)'); + $this->addSql('CREATE INDEX idx_ae_event ON analytics_event (event_name)'); + $this->addSql('CREATE INDEX idx_ae_created ON analytics_event (created_at)'); + $this->addSql('CREATE TABLE analytics_uniq_id (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, uid VARCHAR(36) NOT NULL, hash VARCHAR(64) NOT NULL, ip_hash VARCHAR(64) NOT NULL, user_agent VARCHAR(512) NOT NULL, screen_width INT DEFAULT NULL, screen_height INT DEFAULT NULL, language VARCHAR(10) DEFAULT NULL, device_type VARCHAR(10) NOT NULL, os VARCHAR(30) DEFAULT NULL, browser VARCHAR(30) DEFAULT NULL, created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, user_id INT DEFAULT NULL, PRIMARY KEY (id))'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_65C10CC1539B0606 ON analytics_uniq_id (uid)'); + $this->addSql('CREATE INDEX IDX_65C10CC1A76ED395 ON analytics_uniq_id (user_id)'); + $this->addSql('CREATE INDEX idx_analytics_ip ON analytics_uniq_id (ip_hash)'); + $this->addSql('CREATE INDEX idx_analytics_created ON analytics_uniq_id (created_at)'); + $this->addSql('CREATE TABLE email_tracking (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, message_id VARCHAR(64) NOT NULL, recipient VARCHAR(255) NOT NULL, subject VARCHAR(255) NOT NULL, state VARCHAR(10) NOT NULL, sent_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, opened_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, PRIMARY KEY (id))'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_A31A7D55537A1329 ON email_tracking (message_id)'); + $this->addSql('CREATE TABLE messenger_log (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, message_class VARCHAR(255) NOT NULL, message_body TEXT DEFAULT NULL, status VARCHAR(20) NOT NULL, error_message TEXT DEFAULT NULL, stack_trace TEXT DEFAULT NULL, transport_name VARCHAR(255) DEFAULT NULL, retry_count INT NOT NULL, created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, failed_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, PRIMARY KEY (id))'); + $this->addSql('CREATE INDEX idx_messenger_log_status ON messenger_log (status)'); + $this->addSql('CREATE INDEX idx_messenger_log_created_at ON messenger_log (created_at)'); + $this->addSql('CREATE TABLE "user" (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, email VARCHAR(180) NOT NULL, first_name VARCHAR(255) NOT NULL, last_name VARCHAR(255) NOT NULL, roles JSON NOT NULL, password VARCHAR(255) NOT NULL, created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, PRIMARY KEY (id))'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_8D93D649E7927C74 ON "user" (email)'); + $this->addSql('CREATE TABLE messenger_messages (id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, body TEXT NOT NULL, headers TEXT NOT NULL, queue_name VARCHAR(190) NOT NULL, created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, available_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, delivered_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, PRIMARY KEY (id))'); + $this->addSql('CREATE INDEX IDX_75EA56E0FB7336F0E3BD61CE16BA31DBBF396750 ON messenger_messages (queue_name, available_at, delivered_at, id)'); + $this->addSql('ALTER TABLE analytics_event ADD CONSTRAINT FK_9CD0310A70BEE6D FOREIGN KEY (visitor_id) REFERENCES analytics_uniq_id (id) ON DELETE CASCADE NOT DEFERRABLE'); + $this->addSql('ALTER TABLE analytics_uniq_id ADD CONSTRAINT FK_65C10CC1A76ED395 FOREIGN KEY (user_id) REFERENCES "user" (id) ON DELETE SET NULL NOT DEFERRABLE'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE analytics_event DROP CONSTRAINT FK_9CD0310A70BEE6D'); + $this->addSql('ALTER TABLE analytics_uniq_id DROP CONSTRAINT FK_65C10CC1A76ED395'); + $this->addSql('DROP TABLE analytics_event'); + $this->addSql('DROP TABLE analytics_uniq_id'); + $this->addSql('DROP TABLE email_tracking'); + $this->addSql('DROP TABLE messenger_log'); + $this->addSql('DROP TABLE "user"'); + $this->addSql('DROP TABLE messenger_messages'); + } +} diff --git a/migrations/Version20260330205021.php b/migrations/Version20260330205021.php new file mode 100644 index 0000000..8436b1d --- /dev/null +++ b/migrations/Version20260330205021.php @@ -0,0 +1,31 @@ +addSql('ALTER TABLE email_tracking ADD html_body TEXT DEFAULT NULL'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE email_tracking DROP html_body'); + } +} diff --git a/migrations/Version20260330205802.php b/migrations/Version20260330205802.php new file mode 100644 index 0000000..f2d4bcd --- /dev/null +++ b/migrations/Version20260330205802.php @@ -0,0 +1,37 @@ +addSql('CREATE TABLE attestation (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, reference VARCHAR(64) NOT NULL, type VARCHAR(50) NOT NULL, ip VARCHAR(255) NOT NULL, email VARCHAR(255) NOT NULL, hmac VARCHAR(128) NOT NULL, created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, email_tracking_id INT DEFAULT NULL, PRIMARY KEY (id))'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_326EC63FAEA34913 ON attestation (reference)'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_326EC63F6BC5D709 ON attestation (email_tracking_id)'); + $this->addSql('ALTER TABLE attestation ADD CONSTRAINT FK_326EC63F6BC5D709 FOREIGN KEY (email_tracking_id) REFERENCES email_tracking (id) ON DELETE SET NULL NOT DEFERRABLE'); + $this->addSql('ALTER TABLE email_tracking ADD attachments JSON DEFAULT NULL'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE attestation DROP CONSTRAINT FK_326EC63F6BC5D709'); + $this->addSql('DROP TABLE attestation'); + $this->addSql('ALTER TABLE email_tracking DROP attachments'); + } +} diff --git a/migrations/Version20260330220730.php b/migrations/Version20260330220730.php new file mode 100644 index 0000000..1051fad --- /dev/null +++ b/migrations/Version20260330220730.php @@ -0,0 +1,33 @@ +addSql('ALTER TABLE attestation ADD pdf_path VARCHAR(500) DEFAULT NULL'); + $this->addSql('ALTER TABLE attestation ADD status VARCHAR(20) DEFAULT \'pending\' NOT NULL'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE attestation DROP pdf_path'); + $this->addSql('ALTER TABLE attestation DROP status'); + } +} diff --git a/migrations/Version20260330221027.php b/migrations/Version20260330221027.php new file mode 100644 index 0000000..dd6f634 --- /dev/null +++ b/migrations/Version20260330221027.php @@ -0,0 +1,31 @@ +addSql('ALTER TABLE attestation ADD envelope_id VARCHAR(100) DEFAULT NULL'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE attestation DROP envelope_id'); + } +} diff --git a/migrations/Version20260331081951.php b/migrations/Version20260331081951.php new file mode 100644 index 0000000..2d0acbf --- /dev/null +++ b/migrations/Version20260331081951.php @@ -0,0 +1,54 @@ +addSql('CREATE TABLE sign_document (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, document_id VARCHAR(64) NOT NULL, title VARCHAR(255) NOT NULL, dest_email VARCHAR(255) NOT NULL, dest_name VARCHAR(255) NOT NULL, file_name VARCHAR(255) DEFAULT NULL, state VARCHAR(20) NOT NULL, hmac VARCHAR(128) NOT NULL, created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, sent_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, signed_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, refused_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, refused_reason TEXT DEFAULT NULL, path_pdf_unsigned VARCHAR(500) DEFAULT NULL, path_pdf_signed VARCHAR(500) DEFAULT NULL, path_pdf_signed_certificate VARCHAR(500) DEFAULT NULL, file_hmac VARCHAR(128) DEFAULT NULL, updated_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, PRIMARY KEY (id))'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_1BD06C91C33F7837 ON sign_document (document_id)'); + $this->addSql('CREATE TABLE sign_document_event (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, type VARCHAR(30) NOT NULL, detail TEXT DEFAULT NULL, created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, document_id INT NOT NULL, PRIMARY KEY (id))'); + $this->addSql('CREATE INDEX IDX_D494C99C33F7837 ON sign_document_event (document_id)'); + $this->addSql('CREATE TABLE sign_document_field (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, type VARCHAR(30) NOT NULL, label VARCHAR(255) NOT NULL, page INT NOT NULL, position_x DOUBLE PRECISION NOT NULL, position_y DOUBLE PRECISION NOT NULL, width DOUBLE PRECISION NOT NULL, height DOUBLE PRECISION NOT NULL, position INT NOT NULL, value TEXT DEFAULT NULL, filled_value TEXT DEFAULT NULL, filled_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, required BOOLEAN NOT NULL, document_id INT NOT NULL, signer_id INT DEFAULT NULL, filled_by_id INT DEFAULT NULL, PRIMARY KEY (id))'); + $this->addSql('CREATE INDEX IDX_6D120366C33F7837 ON sign_document_field (document_id)'); + $this->addSql('CREATE INDEX IDX_6D1203669588C067 ON sign_document_field (signer_id)'); + $this->addSql('CREATE INDEX IDX_6D12036690409A66 ON sign_document_field (filled_by_id)'); + $this->addSql('CREATE TABLE sign_document_signer (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, email VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, signing_order INT NOT NULL, state VARCHAR(20) NOT NULL, signed_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, refused_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, refused_reason TEXT DEFAULT NULL, token VARCHAR(64) NOT NULL, created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, document_id INT NOT NULL, PRIMARY KEY (id))'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_2B38977F5F37A13B ON sign_document_signer (token)'); + $this->addSql('CREATE INDEX IDX_2B38977FC33F7837 ON sign_document_signer (document_id)'); + $this->addSql('ALTER TABLE sign_document_event ADD CONSTRAINT FK_D494C99C33F7837 FOREIGN KEY (document_id) REFERENCES sign_document (id) ON DELETE CASCADE NOT DEFERRABLE'); + $this->addSql('ALTER TABLE sign_document_field ADD CONSTRAINT FK_6D120366C33F7837 FOREIGN KEY (document_id) REFERENCES sign_document (id) ON DELETE CASCADE NOT DEFERRABLE'); + $this->addSql('ALTER TABLE sign_document_field ADD CONSTRAINT FK_6D1203669588C067 FOREIGN KEY (signer_id) REFERENCES sign_document_signer (id) ON DELETE SET NULL NOT DEFERRABLE'); + $this->addSql('ALTER TABLE sign_document_field ADD CONSTRAINT FK_6D12036690409A66 FOREIGN KEY (filled_by_id) REFERENCES sign_document_signer (id) ON DELETE SET NULL NOT DEFERRABLE'); + $this->addSql('ALTER TABLE sign_document_signer ADD CONSTRAINT FK_2B38977FC33F7837 FOREIGN KEY (document_id) REFERENCES sign_document (id) ON DELETE CASCADE NOT DEFERRABLE'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE sign_document_event DROP CONSTRAINT FK_D494C99C33F7837'); + $this->addSql('ALTER TABLE sign_document_field DROP CONSTRAINT FK_6D120366C33F7837'); + $this->addSql('ALTER TABLE sign_document_field DROP CONSTRAINT FK_6D1203669588C067'); + $this->addSql('ALTER TABLE sign_document_field DROP CONSTRAINT FK_6D12036690409A66'); + $this->addSql('ALTER TABLE sign_document_signer DROP CONSTRAINT FK_2B38977FC33F7837'); + $this->addSql('DROP TABLE sign_document'); + $this->addSql('DROP TABLE sign_document_event'); + $this->addSql('DROP TABLE sign_document_field'); + $this->addSql('DROP TABLE sign_document_signer'); + } +} diff --git a/migrations/Version20260331082809.php b/migrations/Version20260331082809.php new file mode 100644 index 0000000..599ad07 --- /dev/null +++ b/migrations/Version20260331082809.php @@ -0,0 +1,65 @@ +addSql('ALTER TABLE sign_document_event DROP CONSTRAINT fk_d494c99c33f7837'); + $this->addSql('ALTER TABLE sign_document_field DROP CONSTRAINT fk_6d120366c33f7837'); + $this->addSql('ALTER TABLE sign_document_field DROP CONSTRAINT fk_6d1203669588c067'); + $this->addSql('ALTER TABLE sign_document_field DROP CONSTRAINT fk_6d12036690409a66'); + $this->addSql('ALTER TABLE sign_document_signer DROP CONSTRAINT fk_2b38977fc33f7837'); + $this->addSql('DROP TABLE sign_document'); + $this->addSql('DROP TABLE sign_document_event'); + $this->addSql('DROP TABLE sign_document_field'); + $this->addSql('DROP TABLE sign_document_signer'); + $this->addSql('ALTER TABLE attestation ADD pdf_file_signed VARCHAR(500) DEFAULT NULL'); + $this->addSql('ALTER TABLE attestation ADD pdf_file_certificate VARCHAR(500) DEFAULT NULL'); + $this->addSql('ALTER TABLE attestation ADD submitter_id INT DEFAULT NULL'); + $this->addSql('ALTER TABLE attestation DROP envelope_id'); + $this->addSql('ALTER TABLE attestation RENAME COLUMN pdf_path TO pdf_file_unsigned'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('CREATE TABLE sign_document (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, document_id VARCHAR(64) NOT NULL, title VARCHAR(255) NOT NULL, dest_email VARCHAR(255) NOT NULL, dest_name VARCHAR(255) NOT NULL, file_name VARCHAR(255) DEFAULT NULL, state VARCHAR(20) NOT NULL, hmac VARCHAR(128) NOT NULL, created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, sent_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, signed_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, refused_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, refused_reason TEXT DEFAULT NULL, path_pdf_unsigned VARCHAR(500) DEFAULT NULL, path_pdf_signed VARCHAR(500) DEFAULT NULL, path_pdf_signed_certificate VARCHAR(500) DEFAULT NULL, file_hmac VARCHAR(128) DEFAULT NULL, updated_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, PRIMARY KEY (id))'); + $this->addSql('CREATE UNIQUE INDEX uniq_1bd06c91c33f7837 ON sign_document (document_id)'); + $this->addSql('CREATE TABLE sign_document_event (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, type VARCHAR(30) NOT NULL, detail TEXT DEFAULT NULL, created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, document_id INT NOT NULL, PRIMARY KEY (id))'); + $this->addSql('CREATE INDEX idx_d494c99c33f7837 ON sign_document_event (document_id)'); + $this->addSql('CREATE TABLE sign_document_field (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, type VARCHAR(30) NOT NULL, label VARCHAR(255) NOT NULL, page INT NOT NULL, position_x DOUBLE PRECISION NOT NULL, position_y DOUBLE PRECISION NOT NULL, width DOUBLE PRECISION NOT NULL, height DOUBLE PRECISION NOT NULL, "position" INT NOT NULL, value TEXT DEFAULT NULL, filled_value TEXT DEFAULT NULL, filled_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, required BOOLEAN NOT NULL, document_id INT NOT NULL, signer_id INT DEFAULT NULL, filled_by_id INT DEFAULT NULL, PRIMARY KEY (id))'); + $this->addSql('CREATE INDEX idx_6d120366c33f7837 ON sign_document_field (document_id)'); + $this->addSql('CREATE INDEX idx_6d1203669588c067 ON sign_document_field (signer_id)'); + $this->addSql('CREATE INDEX idx_6d12036690409a66 ON sign_document_field (filled_by_id)'); + $this->addSql('CREATE TABLE sign_document_signer (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, email VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, signing_order INT NOT NULL, state VARCHAR(20) NOT NULL, signed_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, refused_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, refused_reason TEXT DEFAULT NULL, token VARCHAR(64) NOT NULL, created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, document_id INT NOT NULL, PRIMARY KEY (id))'); + $this->addSql('CREATE UNIQUE INDEX uniq_2b38977f5f37a13b ON sign_document_signer (token)'); + $this->addSql('CREATE INDEX idx_2b38977fc33f7837 ON sign_document_signer (document_id)'); + $this->addSql('ALTER TABLE sign_document_event ADD CONSTRAINT fk_d494c99c33f7837 FOREIGN KEY (document_id) REFERENCES sign_document (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE sign_document_field ADD CONSTRAINT fk_6d120366c33f7837 FOREIGN KEY (document_id) REFERENCES sign_document (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE sign_document_field ADD CONSTRAINT fk_6d1203669588c067 FOREIGN KEY (signer_id) REFERENCES sign_document_signer (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE sign_document_field ADD CONSTRAINT fk_6d12036690409a66 FOREIGN KEY (filled_by_id) REFERENCES sign_document_signer (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE sign_document_signer ADD CONSTRAINT fk_2b38977fc33f7837 FOREIGN KEY (document_id) REFERENCES sign_document (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE attestation ADD pdf_path VARCHAR(500) DEFAULT NULL'); + $this->addSql('ALTER TABLE attestation ADD envelope_id VARCHAR(100) DEFAULT NULL'); + $this->addSql('ALTER TABLE attestation DROP pdf_file_unsigned'); + $this->addSql('ALTER TABLE attestation DROP pdf_file_signed'); + $this->addSql('ALTER TABLE attestation DROP pdf_file_certificate'); + $this->addSql('ALTER TABLE attestation DROP submitter_id'); + } +} diff --git a/migrations/Version20260331194615.php b/migrations/Version20260331194615.php new file mode 100644 index 0000000..552c90b --- /dev/null +++ b/migrations/Version20260331194615.php @@ -0,0 +1,33 @@ +addSql('ALTER TABLE "user" ADD keycloak_id VARCHAR(255) DEFAULT NULL'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_8D93D649491914B1 ON "user" (keycloak_id)'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('DROP INDEX UNIQ_8D93D649491914B1'); + $this->addSql('ALTER TABLE "user" DROP keycloak_id'); + } +} diff --git a/migrations/Version20260401065447.php b/migrations/Version20260401065447.php new file mode 100644 index 0000000..ac87689 --- /dev/null +++ b/migrations/Version20260401065447.php @@ -0,0 +1,31 @@ +addSql('ALTER TABLE "user" ADD temp_password VARCHAR(255) DEFAULT NULL'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE "user" DROP temp_password'); + } +} diff --git a/migrations/Version20260401070718.php b/migrations/Version20260401070718.php new file mode 100644 index 0000000..ad3d0db --- /dev/null +++ b/migrations/Version20260401070718.php @@ -0,0 +1,43 @@ +addSql('CREATE TABLE service (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, name VARCHAR(255) NOT NULL, slug VARCHAR(50) NOT NULL, url VARCHAR(500) DEFAULT NULL, status VARCHAR(20) NOT NULL, message TEXT DEFAULT NULL, position INT NOT NULL, created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, updated_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, category_id INT NOT NULL, PRIMARY KEY (id))'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_E19D9AD2989D9B62 ON service (slug)'); + $this->addSql('CREATE INDEX IDX_E19D9AD212469DE2 ON service (category_id)'); + $this->addSql('CREATE TABLE service_category (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, name VARCHAR(255) NOT NULL, slug VARCHAR(50) NOT NULL, position INT NOT NULL, created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, PRIMARY KEY (id))'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_FF3A42FC989D9B62 ON service_category (slug)'); + $this->addSql('CREATE TABLE service_status_history (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, status VARCHAR(20) NOT NULL, message TEXT DEFAULT NULL, created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, service_id INT NOT NULL, PRIMARY KEY (id))'); + $this->addSql('CREATE INDEX IDX_2C0C5A78ED5CA9E6 ON service_status_history (service_id)'); + $this->addSql('ALTER TABLE service ADD CONSTRAINT FK_E19D9AD212469DE2 FOREIGN KEY (category_id) REFERENCES service_category (id) ON DELETE CASCADE NOT DEFERRABLE'); + $this->addSql('ALTER TABLE service_status_history ADD CONSTRAINT FK_2C0C5A78ED5CA9E6 FOREIGN KEY (service_id) REFERENCES service (id) ON DELETE CASCADE NOT DEFERRABLE'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE service DROP CONSTRAINT FK_E19D9AD212469DE2'); + $this->addSql('ALTER TABLE service_status_history DROP CONSTRAINT FK_2C0C5A78ED5CA9E6'); + $this->addSql('DROP TABLE service'); + $this->addSql('DROP TABLE service_category'); + $this->addSql('DROP TABLE service_status_history'); + } +} diff --git a/migrations/Version20260401072355.php b/migrations/Version20260401072355.php new file mode 100644 index 0000000..dc5ec92 --- /dev/null +++ b/migrations/Version20260401072355.php @@ -0,0 +1,33 @@ +addSql('ALTER TABLE service ADD is_external BOOLEAN DEFAULT false NOT NULL'); + $this->addSql('ALTER TABLE service ADD external_type VARCHAR(50) DEFAULT NULL'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE service DROP is_external'); + $this->addSql('ALTER TABLE service DROP external_type'); + } +} diff --git a/migrations/Version20260401073322.php b/migrations/Version20260401073322.php new file mode 100644 index 0000000..1db7065 --- /dev/null +++ b/migrations/Version20260401073322.php @@ -0,0 +1,45 @@ +addSql('CREATE TABLE service_log (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, from_status VARCHAR(20) NOT NULL, to_status VARCHAR(20) NOT NULL, source VARCHAR(50) NOT NULL, created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, service_id INT NOT NULL, changed_by_id INT DEFAULT NULL, PRIMARY KEY (id))'); + $this->addSql('CREATE INDEX IDX_AD6F1BB2ED5CA9E6 ON service_log (service_id)'); + $this->addSql('CREATE INDEX IDX_AD6F1BB2828AD0A0 ON service_log (changed_by_id)'); + $this->addSql('CREATE TABLE service_message (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, title VARCHAR(255) NOT NULL, content TEXT NOT NULL, severity VARCHAR(20) NOT NULL, is_active BOOLEAN NOT NULL, created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, resolved_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, service_id INT NOT NULL, author_id INT DEFAULT NULL, PRIMARY KEY (id))'); + $this->addSql('CREATE INDEX IDX_793962E3ED5CA9E6 ON service_message (service_id)'); + $this->addSql('CREATE INDEX IDX_793962E3F675F31B ON service_message (author_id)'); + $this->addSql('ALTER TABLE service_log ADD CONSTRAINT FK_AD6F1BB2ED5CA9E6 FOREIGN KEY (service_id) REFERENCES service (id) ON DELETE CASCADE NOT DEFERRABLE'); + $this->addSql('ALTER TABLE service_log ADD CONSTRAINT FK_AD6F1BB2828AD0A0 FOREIGN KEY (changed_by_id) REFERENCES "user" (id) ON DELETE SET NULL NOT DEFERRABLE'); + $this->addSql('ALTER TABLE service_message ADD CONSTRAINT FK_793962E3ED5CA9E6 FOREIGN KEY (service_id) REFERENCES service (id) ON DELETE CASCADE NOT DEFERRABLE'); + $this->addSql('ALTER TABLE service_message ADD CONSTRAINT FK_793962E3F675F31B FOREIGN KEY (author_id) REFERENCES "user" (id) ON DELETE SET NULL NOT DEFERRABLE'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE service_log DROP CONSTRAINT FK_AD6F1BB2ED5CA9E6'); + $this->addSql('ALTER TABLE service_log DROP CONSTRAINT FK_AD6F1BB2828AD0A0'); + $this->addSql('ALTER TABLE service_message DROP CONSTRAINT FK_793962E3ED5CA9E6'); + $this->addSql('ALTER TABLE service_message DROP CONSTRAINT FK_793962E3F675F31B'); + $this->addSql('DROP TABLE service_log'); + $this->addSql('DROP TABLE service_message'); + } +} diff --git a/migrations/Version20260401080346.php b/migrations/Version20260401080346.php new file mode 100644 index 0000000..6a33763 --- /dev/null +++ b/migrations/Version20260401080346.php @@ -0,0 +1,35 @@ +addSql('CREATE TABLE revendeur (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, code_revendeur VARCHAR(10) NOT NULL, raison_sociale VARCHAR(255) DEFAULT NULL, siret VARCHAR(14) DEFAULT NULL, address VARCHAR(500) DEFAULT NULL, zip_code VARCHAR(10) DEFAULT NULL, city VARCHAR(255) DEFAULT NULL, email VARCHAR(255) DEFAULT NULL, phone VARCHAR(20) DEFAULT NULL, stripe_connect_id VARCHAR(255) DEFAULT NULL, is_use_stripe BOOLEAN NOT NULL, is_active BOOLEAN NOT NULL, created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, updated_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, user_id INT NOT NULL, PRIMARY KEY (id))'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_47CF17CAEB8A6FF2 ON revendeur (code_revendeur)'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_47CF17CAA76ED395 ON revendeur (user_id)'); + $this->addSql('ALTER TABLE revendeur ADD CONSTRAINT FK_47CF17CAA76ED395 FOREIGN KEY (user_id) REFERENCES "user" (id) ON DELETE CASCADE NOT DEFERRABLE'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE revendeur DROP CONSTRAINT FK_47CF17CAA76ED395'); + $this->addSql('DROP TABLE revendeur'); + } +} diff --git a/migrations/Version20260401081902.php b/migrations/Version20260401081902.php new file mode 100644 index 0000000..39e885f --- /dev/null +++ b/migrations/Version20260401081902.php @@ -0,0 +1,39 @@ +addSql('ALTER TABLE "user" ADD email_auth_code VARCHAR(255) DEFAULT NULL'); + $this->addSql('ALTER TABLE "user" ADD is_email_auth_enabled BOOLEAN DEFAULT false NOT NULL'); + $this->addSql('ALTER TABLE "user" ADD google_authenticator_secret VARCHAR(255) DEFAULT NULL'); + $this->addSql('ALTER TABLE "user" ADD is_google_auth_enabled BOOLEAN DEFAULT false NOT NULL'); + $this->addSql('ALTER TABLE "user" ADD backup_codes JSON DEFAULT NULL'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE "user" DROP email_auth_code'); + $this->addSql('ALTER TABLE "user" DROP is_email_auth_enabled'); + $this->addSql('ALTER TABLE "user" DROP google_authenticator_secret'); + $this->addSql('ALTER TABLE "user" DROP is_google_auth_enabled'); + $this->addSql('ALTER TABLE "user" DROP backup_codes'); + } +} diff --git a/migrations/Version20260401084515.php b/migrations/Version20260401084515.php new file mode 100644 index 0000000..653c941 --- /dev/null +++ b/migrations/Version20260401084515.php @@ -0,0 +1,33 @@ +addSql('ALTER TABLE "user" ADD avatar VARCHAR(255) DEFAULT NULL'); + $this->addSql('ALTER TABLE "user" ADD updated_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE "user" DROP avatar'); + $this->addSql('ALTER TABLE "user" DROP updated_at'); + } +} diff --git a/migrations/Version20260401093509.php b/migrations/Version20260401093509.php new file mode 100644 index 0000000..ec3c83b --- /dev/null +++ b/migrations/Version20260401093509.php @@ -0,0 +1,34 @@ +addSql('CREATE TABLE customer (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, raison_sociale VARCHAR(255) DEFAULT NULL, first_name VARCHAR(255) DEFAULT NULL, last_name VARCHAR(255) DEFAULT NULL, email VARCHAR(255) DEFAULT NULL, phone VARCHAR(20) DEFAULT NULL, address VARCHAR(500) DEFAULT NULL, address2 VARCHAR(500) DEFAULT NULL, zip_code VARCHAR(10) DEFAULT NULL, city VARCHAR(255) DEFAULT NULL, siret VARCHAR(14) DEFAULT NULL, rcs VARCHAR(50) DEFAULT NULL, num_tva VARCHAR(20) DEFAULT NULL, stripe_customer_id VARCHAR(255) DEFAULT NULL, type_company VARCHAR(30) DEFAULT NULL, state VARCHAR(20) NOT NULL, created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, updated_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, user_id INT NOT NULL, PRIMARY KEY (id))'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_81398E09A76ED395 ON customer (user_id)'); + $this->addSql('ALTER TABLE customer ADD CONSTRAINT FK_81398E09A76ED395 FOREIGN KEY (user_id) REFERENCES "user" (id) ON DELETE CASCADE NOT DEFERRABLE'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE customer DROP CONSTRAINT FK_81398E09A76ED395'); + $this->addSql('DROP TABLE customer'); + } +} diff --git a/migrations/Version20260401093914.php b/migrations/Version20260401093914.php new file mode 100644 index 0000000..597a342 --- /dev/null +++ b/migrations/Version20260401093914.php @@ -0,0 +1,33 @@ +addSql('ALTER TABLE customer ADD code_comptable VARCHAR(50) DEFAULT NULL'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_81398E09F7213B5F ON customer (code_comptable)'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('DROP INDEX UNIQ_81398E09F7213B5F'); + $this->addSql('ALTER TABLE customer DROP code_comptable'); + } +} diff --git a/migrations/Version20260401094341.php b/migrations/Version20260401094341.php new file mode 100644 index 0000000..61b705f --- /dev/null +++ b/migrations/Version20260401094341.php @@ -0,0 +1,37 @@ +addSql('ALTER TABLE revendeur ADD contrat_reseller_pdf_unsigned VARCHAR(500) DEFAULT NULL'); + $this->addSql('ALTER TABLE revendeur ADD contrat_reseller_pdf_signed VARCHAR(500) DEFAULT NULL'); + $this->addSql('ALTER TABLE revendeur ADD contrat_reseller_pdf_audit VARCHAR(500) DEFAULT NULL'); + $this->addSql('ALTER TABLE revendeur ADD contrat_submitter_id INT DEFAULT NULL'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE revendeur DROP contrat_reseller_pdf_unsigned'); + $this->addSql('ALTER TABLE revendeur DROP contrat_reseller_pdf_signed'); + $this->addSql('ALTER TABLE revendeur DROP contrat_reseller_pdf_audit'); + $this->addSql('ALTER TABLE revendeur DROP contrat_submitter_id'); + } +} diff --git a/naf.json b/naf.json new file mode 100644 index 0000000..e733907 --- /dev/null +++ b/naf.json @@ -0,0 +1,6830 @@ +[ + { + "id": "01", + "label": "Culture et production animale, chasse et services annexes" + }, + { + "id": "01.1", + "label": "Cultures non permanentes" + }, + { + "id": "01.11", + "label": "Culture de céréales (à l'exception du riz), de légumineuses et de graines oléagineuses" + }, + { + "id": "01.11Z", + "label": "Culture de céréales (à l'exception du riz), de légumineuses et de graines oléagineuses" + }, + { + "id": "01.12", + "label": "Culture du riz" + }, + { + "id": "01.12Z", + "label": "Culture du riz" + }, + { + "id": "01.13", + "label": "Culture de légumes, de melons, de racines et de tubercules" + }, + { + "id": "01.13Z", + "label": "Culture de légumes, de melons, de racines et de tubercules" + }, + { + "id": "01.14", + "label": "Culture de la canne à sucre" + }, + { + "id": "01.14Z", + "label": "Culture de la canne à sucre" + }, + { + "id": "01.15", + "label": "Culture du tabac" + }, + { + "id": "01.15Z", + "label": "Culture du tabac" + }, + { + "id": "01.16", + "label": "Culture de plantes à fibres" + }, + { + "id": "01.16Z", + "label": "Culture de plantes à fibres" + }, + { + "id": "01.19", + "label": "Autres cultures non permanentes" + }, + { + "id": "01.19Z", + "label": "Autres cultures non permanentes" + }, + { + "id": "01.2", + "label": "Cultures permanentes" + }, + { + "id": "01.21", + "label": "Culture de la vigne" + }, + { + "id": "01.21Z", + "label": "Culture de la vigne" + }, + { + "id": "01.22", + "label": "Culture de fruits tropicaux et subtropicaux" + }, + { + "id": "01.22Z", + "label": "Culture de fruits tropicaux et subtropicaux" + }, + { + "id": "01.23", + "label": "Culture d'agrumes" + }, + { + "id": "01.23Z", + "label": "Culture d'agrumes" + }, + { + "id": "01.24", + "label": "Culture de fruits à pépins et à noyau" + }, + { + "id": "01.24Z", + "label": "Culture de fruits à pépins et à noyau" + }, + { + "id": "01.25", + "label": "Culture d'autres fruits d'arbres ou d'arbustes et de fruits à coque" + }, + { + "id": "01.25Z", + "label": "Culture d'autres fruits d'arbres ou d'arbustes et de fruits à coque" + }, + { + "id": "01.26", + "label": "Culture de fruits oléagineux" + }, + { + "id": "01.26Z", + "label": "Culture de fruits oléagineux" + }, + { + "id": "01.27", + "label": "Culture de plantes à boissons" + }, + { + "id": "01.27Z", + "label": "Culture de plantes à boissons" + }, + { + "id": "01.28", + "label": "Culture de plantes à épices, aromatiques, médicinales et pharmaceutiques" + }, + { + "id": "01.28Z", + "label": "Culture de plantes à épices, aromatiques, médicinales et pharmaceutiques" + }, + { + "id": "01.29", + "label": "Autres cultures permanentes" + }, + { + "id": "01.29Z", + "label": "Autres cultures permanentes" + }, + { + "id": "01.3", + "label": "Reproduction de plantes" + }, + { + "id": "01.30", + "label": "Reproduction de plantes" + }, + { + "id": "01.30Z", + "label": "Reproduction de plantes" + }, + { + "id": "01.4", + "label": "Production animale" + }, + { + "id": "01.41", + "label": "Élevage de vaches laitières" + }, + { + "id": "01.41Z", + "label": "Élevage de vaches laitières" + }, + { + "id": "01.42", + "label": "Élevage d'autres bovins et de buffles" + }, + { + "id": "01.42Z", + "label": "Élevage d'autres bovins et de buffles" + }, + { + "id": "01.43", + "label": "Élevage de chevaux et d'autres équidés" + }, + { + "id": "01.43Z", + "label": "Élevage de chevaux et d'autres équidés" + }, + { + "id": "01.44", + "label": "Élevage de chameaux et d'autres camélidés" + }, + { + "id": "01.44Z", + "label": "Élevage de chameaux et d'autres camélidés" + }, + { + "id": "01.45", + "label": "Élevage d'ovins et de caprins" + }, + { + "id": "01.45Z", + "label": "Élevage d'ovins et de caprins" + }, + { + "id": "01.46", + "label": "Élevage de porcins" + }, + { + "id": "01.46Z", + "label": "Élevage de porcins" + }, + { + "id": "01.47", + "label": "Élevage de volailles" + }, + { + "id": "01.47Z", + "label": "Élevage de volailles" + }, + { + "id": "01.49", + "label": "Élevage d'autres animaux" + }, + { + "id": "01.49Z", + "label": "Élevage d'autres animaux" + }, + { + "id": "01.5", + "label": "Culture et élevage associés" + }, + { + "id": "01.50", + "label": "Culture et élevage associés" + }, + { + "id": "01.50Z", + "label": "Culture et élevage associés" + }, + { + "id": "01.6", + "label": "Activités de soutien à l'agriculture et traitement primaire des récoltes" + }, + { + "id": "01.61", + "label": "Activités de soutien aux cultures" + }, + { + "id": "01.61Z", + "label": "Activités de soutien aux cultures" + }, + { + "id": "01.62", + "label": "Activités de soutien à la production animale" + }, + { + "id": "01.62Z", + "label": "Activités de soutien à la production animale" + }, + { + "id": "01.63", + "label": "Traitement primaire des récoltes" + }, + { + "id": "01.63Z", + "label": "Traitement primaire des récoltes" + }, + { + "id": "01.64", + "label": "Traitement des semences" + }, + { + "id": "01.64Z", + "label": "Traitement des semences" + }, + { + "id": "01.7", + "label": "Chasse, piégeage et services annexes" + }, + { + "id": "01.70", + "label": "Chasse, piégeage et services annexes" + }, + { + "id": "01.70Z", + "label": "Chasse, piégeage et services annexes" + }, + { + "id": "02", + "label": "Sylviculture et exploitation forestière" + }, + { + "id": "02.1", + "label": "Sylviculture et autres activités forestières" + }, + { + "id": "02.10", + "label": "Sylviculture et autres activités forestières" + }, + { + "id": "02.10Z", + "label": "Sylviculture et autres activités forestières" + }, + { + "id": "02.2", + "label": "Exploitation forestière" + }, + { + "id": "02.20", + "label": "Exploitation forestière" + }, + { + "id": "02.20Z", + "label": "Exploitation forestière" + }, + { + "id": "02.3", + "label": "Récolte de produits forestiers non ligneux poussant à l'état sauvage" + }, + { + "id": "02.30", + "label": "Récolte de produits forestiers non ligneux poussant à l'état sauvage" + }, + { + "id": "02.30Z", + "label": "Récolte de produits forestiers non ligneux poussant à l'état sauvage" + }, + { + "id": "02.4", + "label": "Services de soutien à l'exploitation forestière" + }, + { + "id": "02.40", + "label": "Services de soutien à l'exploitation forestière" + }, + { + "id": "02.40Z", + "label": "Services de soutien à l'exploitation forestière" + }, + { + "id": "03", + "label": "Pêche et aquaculture" + }, + { + "id": "03.1", + "label": "Pêche" + }, + { + "id": "03.11", + "label": "Pêche en mer" + }, + { + "id": "03.11Z", + "label": "Pêche en mer" + }, + { + "id": "03.12", + "label": "Pêche en eau douce" + }, + { + "id": "03.12Z", + "label": "Pêche en eau douce" + }, + { + "id": "03.2", + "label": "Aquaculture" + }, + { + "id": "03.21", + "label": "Aquaculture en mer" + }, + { + "id": "03.21Z", + "label": "Aquaculture en mer" + }, + { + "id": "03.22", + "label": "Aquaculture en eau douce" + }, + { + "id": "03.22Z", + "label": "Aquaculture en eau douce" + }, + { + "id": "05", + "label": "Extraction de houille et de lignite" + }, + { + "id": "05.1", + "label": "Extraction de houille" + }, + { + "id": "05.10", + "label": "Extraction de houille" + }, + { + "id": "05.10Z", + "label": "Extraction de houille" + }, + { + "id": "05.2", + "label": "Extraction de lignite" + }, + { + "id": "05.20", + "label": "Extraction de lignite" + }, + { + "id": "05.20Z", + "label": "Extraction de lignite" + }, + { + "id": "06", + "label": "Extraction d'hydrocarbures" + }, + { + "id": "06.1", + "label": "Extraction de pétrole brut" + }, + { + "id": "06.10", + "label": "Extraction de pétrole brut" + }, + { + "id": "06.10Z", + "label": "Extraction de pétrole brut" + }, + { + "id": "06.2", + "label": "Extraction de gaz naturel" + }, + { + "id": "06.20", + "label": "Extraction de gaz naturel" + }, + { + "id": "06.20Z", + "label": "Extraction de gaz naturel" + }, + { + "id": "07", + "label": "Extraction de minerais métalliques" + }, + { + "id": "07.1", + "label": "Extraction de minerais de fer" + }, + { + "id": "07.10", + "label": "Extraction de minerais de fer" + }, + { + "id": "07.10Z", + "label": "Extraction de minerais de fer" + }, + { + "id": "07.2", + "label": "Extraction de minerais de métaux non ferreux" + }, + { + "id": "07.21", + "label": "Extraction de minerais d'uranium et de thorium" + }, + { + "id": "07.21Z", + "label": "Extraction de minerais d'uranium et de thorium" + }, + { + "id": "07.29", + "label": "Extraction d'autres minerais de métaux non ferreux" + }, + { + "id": "07.29Z", + "label": "Extraction d'autres minerais de métaux non ferreux" + }, + { + "id": "08", + "label": "Autres industries extractives" + }, + { + "id": "08.1", + "label": "Extraction de pierres, de sables et d'argiles" + }, + { + "id": "08.11", + "label": "Extraction de pierres ornementales et de construction, de calcaire industriel, de gypse, de craie et d'ardoise" + }, + { + "id": "08.11Z", + "label": "Extraction de pierres ornementales et de construction, de calcaire industriel, de gypse, de craie et d'ardoise" + }, + { + "id": "08.12", + "label": "Exploitation de gravières et sablières, extraction d’argiles et de kaolin" + }, + { + "id": "08.12Z", + "label": "Exploitation de gravières et sablières, extraction d’argiles et de kaolin" + }, + { + "id": "08.9", + "label": "Activités extractives n.c.a." + }, + { + "id": "08.91", + "label": "Extraction des minéraux chimiques et d'engrais minéraux" + }, + { + "id": "08.91Z", + "label": "Extraction des minéraux chimiques et d'engrais minéraux" + }, + { + "id": "08.92", + "label": "Extraction de tourbe" + }, + { + "id": "08.92Z", + "label": "Extraction de tourbe" + }, + { + "id": "08.93", + "label": "Production de sel" + }, + { + "id": "08.93Z", + "label": "Production de sel" + }, + { + "id": "08.99", + "label": "Autres activités extractives n.c.a." + }, + { + "id": "08.99Z", + "label": "Autres activités extractives n.c.a." + }, + { + "id": "09", + "label": "Services de soutien aux industries extractives" + }, + { + "id": "09.1", + "label": "Activités de soutien à l'extraction d'hydrocarbures" + }, + { + "id": "09.10", + "label": "Activités de soutien à l'extraction d'hydrocarbures" + }, + { + "id": "09.10Z", + "label": "Activités de soutien à l'extraction d'hydrocarbures" + }, + { + "id": "09.9", + "label": "Activités de soutien aux autres industries extractives" + }, + { + "id": "09.90", + "label": "Activités de soutien aux autres industries extractives" + }, + { + "id": "09.90Z", + "label": "Activités de soutien aux autres industries extractives" + }, + { + "id": "10", + "label": "Industries alimentaires" + }, + { + "id": "10.1", + "label": "Transformation et conservation de la viande et préparation de produits à base de viande" + }, + { + "id": "10.11", + "label": "Transformation et conservation de la viande de boucherie" + }, + { + "id": "10.11Z", + "label": "Transformation et conservation de la viande de boucherie" + }, + { + "id": "10.12", + "label": "Transformation et conservation de la viande de volaille" + }, + { + "id": "10.12Z", + "label": "Transformation et conservation de la viande de volaille" + }, + { + "id": "10.13", + "label": "Préparation de produits à base de viande" + }, + { + "id": "10.13A", + "label": "Préparation industrielle de produits à base de viande" + }, + { + "id": "10.13B", + "label": "Charcuterie" + }, + { + "id": "10.2", + "label": "Transformation et conservation de poisson, de crustacés et de mollusques" + }, + { + "id": "10.20", + "label": "Transformation et conservation de poisson, de crustacés et de mollusques" + }, + { + "id": "10.20Z", + "label": "Transformation et conservation de poisson, de crustacés et de mollusques" + }, + { + "id": "10.3", + "label": "Transformation et conservation de fruits et légumes" + }, + { + "id": "10.31", + "label": "Transformation et conservation de pommes de terre" + }, + { + "id": "10.31Z", + "label": "Transformation et conservation de pommes de terre" + }, + { + "id": "10.32", + "label": "Préparation de jus de fruits et légumes" + }, + { + "id": "10.32Z", + "label": "Préparation de jus de fruits et légumes" + }, + { + "id": "10.39", + "label": "Autre transformation et conservation de fruits et légumes" + }, + { + "id": "10.39A", + "label": "Autre transformation et conservation de légumes" + }, + { + "id": "10.39B", + "label": "Transformation et conservation de fruits" + }, + { + "id": "10.4", + "label": "Fabrication d’huiles et graisses végétales et animales" + }, + { + "id": "10.41", + "label": "Fabrication d'huiles et graisses" + }, + { + "id": "10.41A", + "label": "Fabrication d'huiles et graisses brutes" + }, + { + "id": "10.41B", + "label": "Fabrication d'huiles et graisses raffinées" + }, + { + "id": "10.42", + "label": "Fabrication de margarine et graisses comestibles similaires" + }, + { + "id": "10.42Z", + "label": "Fabrication de margarine et graisses comestibles similaires" + }, + { + "id": "10.5", + "label": "Fabrication de produits laitiers" + }, + { + "id": "10.51", + "label": "Exploitation de laiteries et fabrication de fromage" + }, + { + "id": "10.51A", + "label": "Fabrication de lait liquide et de produits frais" + }, + { + "id": "10.51B", + "label": "Fabrication de beurre" + }, + { + "id": "10.51C", + "label": "Fabrication de fromage" + }, + { + "id": "10.51D", + "label": "Fabrication d'autres produits laitiers" + }, + { + "id": "10.52", + "label": "Fabrication de glaces et sorbets" + }, + { + "id": "10.52Z", + "label": "Fabrication de glaces et sorbets" + }, + { + "id": "10.6", + "label": "Travail des grains ; fabrication de produits amylacés" + }, + { + "id": "10.61", + "label": "Travail des grains" + }, + { + "id": "10.61A", + "label": "Meunerie" + }, + { + "id": "10.61B", + "label": "Autres activités du travail des grains" + }, + { + "id": "10.62", + "label": "Fabrication de produits amylacés" + }, + { + "id": "10.62Z", + "label": "Fabrication de produits amylacés" + }, + { + "id": "10.7", + "label": "Fabrication de produits de boulangerie-pâtisserie et de pâtes alimentaires" + }, + { + "id": "10.71", + "label": "Fabrication de pain et de pâtisserie fraîche" + }, + { + "id": "10.71A", + "label": "Fabrication industrielle de pain et de pâtisserie fraîche" + }, + { + "id": "10.71B", + "label": "Cuisson de produits de boulangerie" + }, + { + "id": "10.71C", + "label": "Boulangerie et boulangerie-pâtisserie" + }, + { + "id": "10.71D", + "label": "Pâtisserie" + }, + { + "id": "10.72", + "label": "Fabrication de biscuits, biscottes et pâtisseries de conservation" + }, + { + "id": "10.72Z", + "label": "Fabrication de biscuits, biscottes et pâtisseries de conservation" + }, + { + "id": "10.73", + "label": "Fabrication de pâtes alimentaires" + }, + { + "id": "10.73Z", + "label": "Fabrication de pâtes alimentaires" + }, + { + "id": "10.8", + "label": "Fabrication d'autres produits alimentaires" + }, + { + "id": "10.81", + "label": "Fabrication de sucre" + }, + { + "id": "10.81Z", + "label": "Fabrication de sucre" + }, + { + "id": "10.82", + "label": "Fabrication de cacao, chocolat et de produits de confiserie" + }, + { + "id": "10.82Z", + "label": "Fabrication de cacao, chocolat et de produits de confiserie" + }, + { + "id": "10.83", + "label": "Transformation du thé et du café" + }, + { + "id": "10.83Z", + "label": "Transformation du thé et du café" + }, + { + "id": "10.84", + "label": "Fabrication de condiments et assaisonnements" + }, + { + "id": "10.84Z", + "label": "Fabrication de condiments et assaisonnements" + }, + { + "id": "10.85", + "label": "Fabrication de plats préparés" + }, + { + "id": "10.85Z", + "label": "Fabrication de plats préparés" + }, + { + "id": "10.86", + "label": "Fabrication d'aliments homogénéisés et diététiques" + }, + { + "id": "10.86Z", + "label": "Fabrication d'aliments homogénéisés et diététiques" + }, + { + "id": "10.89", + "label": "Fabrication d'autres produits alimentaires n.c.a." + }, + { + "id": "10.89Z", + "label": "Fabrication d'autres produits alimentaires n.c.a." + }, + { + "id": "10.9", + "label": "Fabrication d'aliments pour animaux" + }, + { + "id": "10.91", + "label": "Fabrication d'aliments pour animaux de ferme" + }, + { + "id": "10.91Z", + "label": "Fabrication d'aliments pour animaux de ferme" + }, + { + "id": "10.92", + "label": "Fabrication d'aliments pour animaux de compagnie" + }, + { + "id": "10.92Z", + "label": "Fabrication d'aliments pour animaux de compagnie" + }, + { + "id": "11", + "label": "Fabrication de boissons" + }, + { + "id": "11.0", + "label": "Fabrication de boissons" + }, + { + "id": "11.01", + "label": "Production de boissons alcooliques distillées" + }, + { + "id": "11.01Z", + "label": "Production de boissons alcooliques distillées" + }, + { + "id": "11.02", + "label": "Production de vin (de raisin)" + }, + { + "id": "11.02A", + "label": "Fabrication de vins effervescents" + }, + { + "id": "11.02B", + "label": "Vinification" + }, + { + "id": "11.03", + "label": "Fabrication de cidre et de vins de fruits" + }, + { + "id": "11.03Z", + "label": "Fabrication de cidre et de vins de fruits" + }, + { + "id": "11.04", + "label": "Production d'autres boissons fermentées non distillées" + }, + { + "id": "11.04Z", + "label": "Production d'autres boissons fermentées non distillées" + }, + { + "id": "11.05", + "label": "Fabrication de bière" + }, + { + "id": "11.05Z", + "label": "Fabrication de bière" + }, + { + "id": "11.06", + "label": "Fabrication de malt" + }, + { + "id": "11.06Z", + "label": "Fabrication de malt" + }, + { + "id": "11.07", + "label": "Industrie des eaux minérales et autres eaux embouteillées et des boissons rafraîchissantes" + }, + { + "id": "11.07A", + "label": "Industrie des eaux de table" + }, + { + "id": "11.07B", + "label": "Production de boissons rafraîchissantes" + }, + { + "id": "12", + "label": "Fabrication de produits à base de tabac" + }, + { + "id": "12.0", + "label": "Fabrication de produits à base de tabac" + }, + { + "id": "12.00", + "label": "Fabrication de produits à base de tabac" + }, + { + "id": "12.00Z", + "label": "Fabrication de produits à base de tabac" + }, + { + "id": "13", + "label": "Fabrication de textiles" + }, + { + "id": "13.1", + "label": "Préparation de fibres textiles et filature" + }, + { + "id": "13.10", + "label": "Préparation de fibres textiles et filature" + }, + { + "id": "13.10Z", + "label": "Préparation de fibres textiles et filature" + }, + { + "id": "13.2", + "label": "Tissage" + }, + { + "id": "13.20", + "label": "Tissage" + }, + { + "id": "13.20Z", + "label": "Tissage" + }, + { + "id": "13.3", + "label": "Ennoblissement textile" + }, + { + "id": "13.30", + "label": "Ennoblissement textile" + }, + { + "id": "13.30Z", + "label": "Ennoblissement textile" + }, + { + "id": "13.9", + "label": "Fabrication d'autres textiles" + }, + { + "id": "13.91", + "label": "Fabrication d'étoffes à mailles" + }, + { + "id": "13.91Z", + "label": "Fabrication d'étoffes à mailles" + }, + { + "id": "13.92", + "label": "Fabrication d'articles textiles, sauf habillement" + }, + { + "id": "13.92Z", + "label": "Fabrication d'articles textiles, sauf habillement" + }, + { + "id": "13.93", + "label": "Fabrication de tapis et moquettes" + }, + { + "id": "13.93Z", + "label": "Fabrication de tapis et moquettes" + }, + { + "id": "13.94", + "label": "Fabrication de ficelles, cordes et filets" + }, + { + "id": "13.94Z", + "label": "Fabrication de ficelles, cordes et filets" + }, + { + "id": "13.95", + "label": "Fabrication de non-tissés, sauf habillement" + }, + { + "id": "13.95Z", + "label": "Fabrication de non-tissés, sauf habillement" + }, + { + "id": "13.96", + "label": "Fabrication d'autres textiles techniques et industriels" + }, + { + "id": "13.96Z", + "label": "Fabrication d'autres textiles techniques et industriels" + }, + { + "id": "13.99", + "label": "Fabrication d'autres textiles n.c.a." + }, + { + "id": "13.99Z", + "label": "Fabrication d'autres textiles n.c.a." + }, + { + "id": "14", + "label": "Industrie de l'habillement" + }, + { + "id": "14.1", + "label": "Fabrication de vêtements, autres qu'en fourrure" + }, + { + "id": "14.11", + "label": "Fabrication de vêtements en cuir" + }, + { + "id": "14.11Z", + "label": "Fabrication de vêtements en cuir" + }, + { + "id": "14.12", + "label": "Fabrication de vêtements de travail" + }, + { + "id": "14.12Z", + "label": "Fabrication de vêtements de travail" + }, + { + "id": "14.13", + "label": "Fabrication de vêtements de dessus" + }, + { + "id": "14.13Z", + "label": "Fabrication de vêtements de dessus" + }, + { + "id": "14.14", + "label": "Fabrication de vêtements de dessous" + }, + { + "id": "14.14Z", + "label": "Fabrication de vêtements de dessous" + }, + { + "id": "14.19", + "label": "Fabrication d'autres vêtements et accessoires" + }, + { + "id": "14.19Z", + "label": "Fabrication d'autres vêtements et accessoires" + }, + { + "id": "14.2", + "label": "Fabrication d'articles en fourrure" + }, + { + "id": "14.20", + "label": "Fabrication d'articles en fourrure" + }, + { + "id": "14.20Z", + "label": "Fabrication d'articles en fourrure" + }, + { + "id": "14.3", + "label": "Fabrication d'articles à mailles" + }, + { + "id": "14.31", + "label": "Fabrication d'articles chaussants à mailles" + }, + { + "id": "14.31Z", + "label": "Fabrication d'articles chaussants à mailles" + }, + { + "id": "14.39", + "label": "Fabrication d'autres articles à mailles" + }, + { + "id": "14.39Z", + "label": "Fabrication d'autres articles à mailles" + }, + { + "id": "15", + "label": "Industrie du cuir et de la chaussure" + }, + { + "id": "15.1", + "label": "Apprêt et tannage des cuirs ; préparation et teinture des fourrures ; fabrication d'articles de voyage, de maroquinerie et de sellerie" + }, + { + "id": "15.11", + "label": "Apprêt et tannage des cuirs ; préparation et teinture des fourrures" + }, + { + "id": "15.11Z", + "label": "Apprêt et tannage des cuirs ; préparation et teinture des fourrures" + }, + { + "id": "15.12", + "label": "Fabrication d'articles de voyage, de maroquinerie et de sellerie" + }, + { + "id": "15.12Z", + "label": "Fabrication d'articles de voyage, de maroquinerie et de sellerie" + }, + { + "id": "15.2", + "label": "Fabrication de chaussures" + }, + { + "id": "15.20", + "label": "Fabrication de chaussures" + }, + { + "id": "15.20Z", + "label": "Fabrication de chaussures" + }, + { + "id": "16", + "label": "Travail du bois et fabrication d'articles en bois et en liège, à l’exception des meubles ; fabrication d’articles en vannerie et sparterie" + }, + { + "id": "16.1", + "label": "Sciage et rabotage du bois" + }, + { + "id": "16.10", + "label": "Sciage et rabotage du bois" + }, + { + "id": "16.10A", + "label": "Sciage et rabotage du bois, hors imprégnation" + }, + { + "id": "16.10B", + "label": "Imprégnation du bois" + }, + { + "id": "16.2", + "label": "Fabrication d'articles en bois, liège, vannerie et sparterie" + }, + { + "id": "16.21", + "label": "Fabrication de placage et de panneaux de bois" + }, + { + "id": "16.21Z", + "label": "Fabrication de placage et de panneaux de bois" + }, + { + "id": "16.22", + "label": "Fabrication de parquets assemblés" + }, + { + "id": "16.22Z", + "label": "Fabrication de parquets assemblés" + }, + { + "id": "16.23", + "label": "Fabrication de charpentes et d'autres menuiseries" + }, + { + "id": "16.23Z", + "label": "Fabrication de charpentes et d'autres menuiseries" + }, + { + "id": "16.24", + "label": "Fabrication d'emballages en bois" + }, + { + "id": "16.24Z", + "label": "Fabrication d'emballages en bois" + }, + { + "id": "16.29", + "label": "Fabrication d'objets divers en bois ; fabrication d'objets en liège, vannerie et sparterie" + }, + { + "id": "16.29Z", + "label": "Fabrication d'objets divers en bois ; fabrication d'objets en liège, vannerie et sparterie" + }, + { + "id": "17", + "label": "Industrie du papier et du carton" + }, + { + "id": "17.1", + "label": "Fabrication de pâte à papier, de papier et de carton" + }, + { + "id": "17.11", + "label": "Fabrication de pâte à papier" + }, + { + "id": "17.11Z", + "label": "Fabrication de pâte à papier" + }, + { + "id": "17.12", + "label": "Fabrication de papier et de carton" + }, + { + "id": "17.12Z", + "label": "Fabrication de papier et de carton" + }, + { + "id": "17.2", + "label": "Fabrication d'articles en papier ou en carton" + }, + { + "id": "17.21", + "label": "Fabrication de papier et carton ondulés et d'emballages en papier ou en carton" + }, + { + "id": "17.21A", + "label": "Fabrication de carton ondulé" + }, + { + "id": "17.21B", + "label": "Fabrication de cartonnages" + }, + { + "id": "17.21C", + "label": "Fabrication d'emballages en papier" + }, + { + "id": "17.22", + "label": "Fabrication d'articles en papier à usage sanitaire ou domestique" + }, + { + "id": "17.22Z", + "label": "Fabrication d'articles en papier à usage sanitaire ou domestique" + }, + { + "id": "17.23", + "label": "Fabrication d'articles de papeterie" + }, + { + "id": "17.23Z", + "label": "Fabrication d'articles de papeterie" + }, + { + "id": "17.24", + "label": "Fabrication de papiers peints" + }, + { + "id": "17.24Z", + "label": "Fabrication de papiers peints" + }, + { + "id": "17.29", + "label": "Fabrication d'autres articles en papier ou en carton" + }, + { + "id": "17.29Z", + "label": "Fabrication d'autres articles en papier ou en carton" + }, + { + "id": "18", + "label": "Imprimerie et reproduction d'enregistrements" + }, + { + "id": "18.1", + "label": "Imprimerie et services annexes" + }, + { + "id": "18.11", + "label": "Imprimerie de journaux" + }, + { + "id": "18.11Z", + "label": "Imprimerie de journaux" + }, + { + "id": "18.12", + "label": "Autre imprimerie (labeur)" + }, + { + "id": "18.12Z", + "label": "Autre imprimerie (labeur)" + }, + { + "id": "18.13", + "label": "Activités de pré-presse" + }, + { + "id": "18.13Z", + "label": "Activités de pré-presse" + }, + { + "id": "18.14", + "label": "Reliure et activités connexes" + }, + { + "id": "18.14Z", + "label": "Reliure et activités connexes" + }, + { + "id": "18.2", + "label": "Reproduction d'enregistrements" + }, + { + "id": "18.20", + "label": "Reproduction d'enregistrements" + }, + { + "id": "18.20Z", + "label": "Reproduction d'enregistrements" + }, + { + "id": "19", + "label": "Cokéfaction et raffinage" + }, + { + "id": "19.1", + "label": "Cokéfaction" + }, + { + "id": "19.10", + "label": "Cokéfaction" + }, + { + "id": "19.10Z", + "label": "Cokéfaction" + }, + { + "id": "19.2", + "label": "Raffinage du pétrole" + }, + { + "id": "19.20", + "label": "Raffinage du pétrole" + }, + { + "id": "19.20Z", + "label": "Raffinage du pétrole" + }, + { + "id": "20", + "label": "Industrie chimique" + }, + { + "id": "20.1", + "label": "Fabrication de produits chimiques de base, de produits azotés et d'engrais, de matières plastiques de base et de caoutchouc synthétique" + }, + { + "id": "20.11", + "label": "Fabrication de gaz industriels" + }, + { + "id": "20.11Z", + "label": "Fabrication de gaz industriels" + }, + { + "id": "20.12", + "label": "Fabrication de colorants et de pigments" + }, + { + "id": "20.12Z", + "label": "Fabrication de colorants et de pigments" + }, + { + "id": "20.13", + "label": "Fabrication d'autres produits chimiques inorganiques de base" + }, + { + "id": "20.13A", + "label": "Enrichissement et retraitement de matières nucléaires" + }, + { + "id": "20.13B", + "label": "Fabrication d'autres produits chimiques inorganiques de base n.c.a." + }, + { + "id": "20.14", + "label": "Fabrication d'autres produits chimiques organiques de base" + }, + { + "id": "20.14Z", + "label": "Fabrication d'autres produits chimiques organiques de base" + }, + { + "id": "20.15", + "label": "Fabrication de produits azotés et d'engrais" + }, + { + "id": "20.15Z", + "label": "Fabrication de produits azotés et d'engrais" + }, + { + "id": "20.16", + "label": "Fabrication de matières plastiques de base" + }, + { + "id": "20.16Z", + "label": "Fabrication de matières plastiques de base" + }, + { + "id": "20.17", + "label": "Fabrication de caoutchouc synthétique" + }, + { + "id": "20.17Z", + "label": "Fabrication de caoutchouc synthétique" + }, + { + "id": "20.2", + "label": "Fabrication de pesticides et d’autres produits agrochimiques" + }, + { + "id": "20.20", + "label": "Fabrication de pesticides et d’autres produits agrochimiques" + }, + { + "id": "20.20Z", + "label": "Fabrication de pesticides et d’autres produits agrochimiques" + }, + { + "id": "20.3", + "label": "Fabrication de peintures, vernis, encres et mastics" + }, + { + "id": "20.30", + "label": "Fabrication de peintures, vernis, encres et mastics" + }, + { + "id": "20.30Z", + "label": "Fabrication de peintures, vernis, encres et mastics" + }, + { + "id": "20.4", + "label": "Fabrication de savons, de produits d'entretien et de parfums" + }, + { + "id": "20.41", + "label": "Fabrication de savons, détergents et produits d'entretien" + }, + { + "id": "20.41Z", + "label": "Fabrication de savons, détergents et produits d'entretien" + }, + { + "id": "20.42", + "label": "Fabrication de parfums et de produits pour la toilette" + }, + { + "id": "20.42Z", + "label": "Fabrication de parfums et de produits pour la toilette" + }, + { + "id": "20.5", + "label": "Fabrication d'autres produits chimiques" + }, + { + "id": "20.51", + "label": "Fabrication de produits explosifs" + }, + { + "id": "20.51Z", + "label": "Fabrication de produits explosifs" + }, + { + "id": "20.52", + "label": "Fabrication de colles" + }, + { + "id": "20.52Z", + "label": "Fabrication de colles" + }, + { + "id": "20.53", + "label": "Fabrication d'huiles essentielles" + }, + { + "id": "20.53Z", + "label": "Fabrication d'huiles essentielles" + }, + { + "id": "20.59", + "label": "Fabrication d'autres produits chimiques n.c.a." + }, + { + "id": "20.59Z", + "label": "Fabrication d'autres produits chimiques n.c.a." + }, + { + "id": "20.6", + "label": "Fabrication de fibres artificielles ou synthétiques" + }, + { + "id": "20.60", + "label": "Fabrication de fibres artificielles ou synthétiques" + }, + { + "id": "20.60Z", + "label": "Fabrication de fibres artificielles ou synthétiques" + }, + { + "id": "21", + "label": "Industrie pharmaceutique" + }, + { + "id": "21.1", + "label": "Fabrication de produits pharmaceutiques de base" + }, + { + "id": "21.10", + "label": "Fabrication de produits pharmaceutiques de base" + }, + { + "id": "21.10Z", + "label": "Fabrication de produits pharmaceutiques de base" + }, + { + "id": "21.2", + "label": "Fabrication de préparations pharmaceutiques" + }, + { + "id": "21.20", + "label": "Fabrication de préparations pharmaceutiques" + }, + { + "id": "21.20Z", + "label": "Fabrication de préparations pharmaceutiques" + }, + { + "id": "22", + "label": "Fabrication de produits en caoutchouc et en plastique" + }, + { + "id": "22.1", + "label": "Fabrication de produits en caoutchouc" + }, + { + "id": "22.11", + "label": "Fabrication et rechapage de pneumatiques" + }, + { + "id": "22.11Z", + "label": "Fabrication et rechapage de pneumatiques" + }, + { + "id": "22.19", + "label": "Fabrication d'autres articles en caoutchouc" + }, + { + "id": "22.19Z", + "label": "Fabrication d'autres articles en caoutchouc" + }, + { + "id": "22.2", + "label": "Fabrication de produits en plastique" + }, + { + "id": "22.21", + "label": "Fabrication de plaques, feuilles, tubes et profilés en matières plastiques" + }, + { + "id": "22.21Z", + "label": "Fabrication de plaques, feuilles, tubes et profilés en matières plastiques" + }, + { + "id": "22.22", + "label": "Fabrication d'emballages en matières plastiques" + }, + { + "id": "22.22Z", + "label": "Fabrication d'emballages en matières plastiques" + }, + { + "id": "22.23", + "label": "Fabrication d'éléments en matières plastiques pour la construction" + }, + { + "id": "22.23Z", + "label": "Fabrication d'éléments en matières plastiques pour la construction" + }, + { + "id": "22.29", + "label": "Fabrication d'autres articles en matières plastiques" + }, + { + "id": "22.29A", + "label": "Fabrication de pièces techniques à base de matières plastiques" + }, + { + "id": "22.29B", + "label": "Fabrication de produits de consommation courante en matières plastiques" + }, + { + "id": "23", + "label": "Fabrication d'autres produits minéraux non métalliques" + }, + { + "id": "23.1", + "label": "Fabrication de verre et d'articles en verre" + }, + { + "id": "23.11", + "label": "Fabrication de verre plat" + }, + { + "id": "23.11Z", + "label": "Fabrication de verre plat" + }, + { + "id": "23.12", + "label": "Façonnage et transformation du verre plat" + }, + { + "id": "23.12Z", + "label": "Façonnage et transformation du verre plat" + }, + { + "id": "23.13", + "label": "Fabrication de verre creux" + }, + { + "id": "23.13Z", + "label": "Fabrication de verre creux" + }, + { + "id": "23.14", + "label": "Fabrication de fibres de verre" + }, + { + "id": "23.14Z", + "label": "Fabrication de fibres de verre" + }, + { + "id": "23.19", + "label": "Fabrication et façonnage d'autres articles en verre, y compris verre technique" + }, + { + "id": "23.19Z", + "label": "Fabrication et façonnage d'autres articles en verre, y compris verre technique" + }, + { + "id": "23.2", + "label": "Fabrication de produits réfractaires" + }, + { + "id": "23.20", + "label": "Fabrication de produits réfractaires" + }, + { + "id": "23.20Z", + "label": "Fabrication de produits réfractaires" + }, + { + "id": "23.3", + "label": "Fabrication de matériaux de construction en terre cuite" + }, + { + "id": "23.31", + "label": "Fabrication de carreaux en céramique" + }, + { + "id": "23.31Z", + "label": "Fabrication de carreaux en céramique" + }, + { + "id": "23.32", + "label": "Fabrication de briques, tuiles et produits de construction, en terre cuite" + }, + { + "id": "23.32Z", + "label": "Fabrication de briques, tuiles et produits de construction, en terre cuite" + }, + { + "id": "23.4", + "label": "Fabrication d'autres produits en céramique et en porcelaine" + }, + { + "id": "23.41", + "label": "Fabrication d'articles céramiques à usage domestique ou ornemental" + }, + { + "id": "23.41Z", + "label": "Fabrication d'articles céramiques à usage domestique ou ornemental" + }, + { + "id": "23.42", + "label": "Fabrication d'appareils sanitaires en céramique" + }, + { + "id": "23.42Z", + "label": "Fabrication d'appareils sanitaires en céramique" + }, + { + "id": "23.43", + "label": "Fabrication d'isolateurs et pièces isolantes en céramique" + }, + { + "id": "23.43Z", + "label": "Fabrication d'isolateurs et pièces isolantes en céramique" + }, + { + "id": "23.44", + "label": "Fabrication d'autres produits céramiques à usage technique" + }, + { + "id": "23.44Z", + "label": "Fabrication d'autres produits céramiques à usage technique" + }, + { + "id": "23.49", + "label": "Fabrication d'autres produits céramiques" + }, + { + "id": "23.49Z", + "label": "Fabrication d'autres produits céramiques" + }, + { + "id": "23.5", + "label": "Fabrication de ciment, chaux et plâtre" + }, + { + "id": "23.51", + "label": "Fabrication de ciment" + }, + { + "id": "23.51Z", + "label": "Fabrication de ciment" + }, + { + "id": "23.52", + "label": "Fabrication de chaux et plâtre" + }, + { + "id": "23.52Z", + "label": "Fabrication de chaux et plâtre" + }, + { + "id": "23.6", + "label": "Fabrication d'ouvrages en béton, en ciment ou en plâtre" + }, + { + "id": "23.61", + "label": "Fabrication d'éléments en béton pour la construction" + }, + { + "id": "23.61Z", + "label": "Fabrication d'éléments en béton pour la construction" + }, + { + "id": "23.62", + "label": "Fabrication d'éléments en plâtre pour la construction" + }, + { + "id": "23.62Z", + "label": "Fabrication d'éléments en plâtre pour la construction" + }, + { + "id": "23.63", + "label": "Fabrication de béton prêt à l'emploi" + }, + { + "id": "23.63Z", + "label": "Fabrication de béton prêt à l'emploi" + }, + { + "id": "23.64", + "label": "Fabrication de mortiers et bétons secs" + }, + { + "id": "23.64Z", + "label": "Fabrication de mortiers et bétons secs" + }, + { + "id": "23.65", + "label": "Fabrication d'ouvrages en fibre-ciment" + }, + { + "id": "23.65Z", + "label": "Fabrication d'ouvrages en fibre-ciment" + }, + { + "id": "23.69", + "label": "Fabrication d'autres ouvrages en béton, en ciment ou en plâtre" + }, + { + "id": "23.69Z", + "label": "Fabrication d'autres ouvrages en béton, en ciment ou en plâtre" + }, + { + "id": "23.7", + "label": "Taille, façonnage et finissage de pierres" + }, + { + "id": "23.70", + "label": "Taille, façonnage et finissage de pierres" + }, + { + "id": "23.70Z", + "label": "Taille, façonnage et finissage de pierres" + }, + { + "id": "23.9", + "label": "Fabrication de produits abrasifs et de produits minéraux non métalliques n.c.a." + }, + { + "id": "23.91", + "label": "Fabrication de produits abrasifs" + }, + { + "id": "23.91Z", + "label": "Fabrication de produits abrasifs" + }, + { + "id": "23.99", + "label": "Fabrication d'autres produits minéraux non métalliques n.c.a." + }, + { + "id": "23.99Z", + "label": "Fabrication d'autres produits minéraux non métalliques n.c.a." + }, + { + "id": "24", + "label": "Métallurgie" + }, + { + "id": "24.1", + "label": "Sidérurgie" + }, + { + "id": "24.10", + "label": "Sidérurgie" + }, + { + "id": "24.10Z", + "label": "Sidérurgie" + }, + { + "id": "24.2", + "label": "Fabrication de tubes, tuyaux, profilés creux et accessoires correspondants en acier" + }, + { + "id": "24.20", + "label": "Fabrication de tubes, tuyaux, profilés creux et accessoires correspondants en acier" + }, + { + "id": "24.20Z", + "label": "Fabrication de tubes, tuyaux, profilés creux et accessoires correspondants en acier" + }, + { + "id": "24.3", + "label": "Fabrication d'autres produits de première transformation de l'acier" + }, + { + "id": "24.31", + "label": "Étirage à froid de barres" + }, + { + "id": "24.31Z", + "label": "Étirage à froid de barres" + }, + { + "id": "24.32", + "label": "Laminage à froid de feuillards" + }, + { + "id": "24.32Z", + "label": "Laminage à froid de feuillards" + }, + { + "id": "24.33", + "label": "Profilage à froid par formage ou pliage" + }, + { + "id": "24.33Z", + "label": "Profilage à froid par formage ou pliage" + }, + { + "id": "24.34", + "label": "Tréfilage à froid" + }, + { + "id": "24.34Z", + "label": "Tréfilage à froid" + }, + { + "id": "24.4", + "label": "Production de métaux précieux et d'autres métaux non ferreux" + }, + { + "id": "24.41", + "label": "Production de métaux précieux" + }, + { + "id": "24.41Z", + "label": "Production de métaux précieux" + }, + { + "id": "24.42", + "label": "Métallurgie de l'aluminium" + }, + { + "id": "24.42Z", + "label": "Métallurgie de l'aluminium" + }, + { + "id": "24.43", + "label": "Métallurgie du plomb, du zinc ou de l'étain" + }, + { + "id": "24.43Z", + "label": "Métallurgie du plomb, du zinc ou de l'étain" + }, + { + "id": "24.44", + "label": "Métallurgie du cuivre" + }, + { + "id": "24.44Z", + "label": "Métallurgie du cuivre" + }, + { + "id": "24.45", + "label": "Métallurgie des autres métaux non ferreux" + }, + { + "id": "24.45Z", + "label": "Métallurgie des autres métaux non ferreux" + }, + { + "id": "24.46", + "label": "Élaboration et transformation de matières nucléaires" + }, + { + "id": "24.46Z", + "label": "Élaboration et transformation de matières nucléaires" + }, + { + "id": "24.5", + "label": "Fonderie" + }, + { + "id": "24.51", + "label": "Fonderie de fonte" + }, + { + "id": "24.51Z", + "label": "Fonderie de fonte" + }, + { + "id": "24.52", + "label": "Fonderie d'acier" + }, + { + "id": "24.52Z", + "label": "Fonderie d'acier" + }, + { + "id": "24.53", + "label": "Fonderie de métaux légers" + }, + { + "id": "24.53Z", + "label": "Fonderie de métaux légers" + }, + { + "id": "24.54", + "label": "Fonderie d'autres métaux non ferreux" + }, + { + "id": "24.54Z", + "label": "Fonderie d'autres métaux non ferreux" + }, + { + "id": "25", + "label": "Fabrication de produits métalliques, à l’exception des machines et des équipements" + }, + { + "id": "25.1", + "label": "Fabrication d'éléments en métal pour la construction" + }, + { + "id": "25.11", + "label": "Fabrication de structures métalliques et de parties de structures" + }, + { + "id": "25.11Z", + "label": "Fabrication de structures métalliques et de parties de structures" + }, + { + "id": "25.12", + "label": "Fabrication de portes et fenêtres en métal" + }, + { + "id": "25.12Z", + "label": "Fabrication de portes et fenêtres en métal" + }, + { + "id": "25.2", + "label": "Fabrication de réservoirs, citernes et conteneurs métalliques" + }, + { + "id": "25.21", + "label": "Fabrication de radiateurs et de chaudières pour le chauffage central" + }, + { + "id": "25.21Z", + "label": "Fabrication de radiateurs et de chaudières pour le chauffage central" + }, + { + "id": "25.29", + "label": "Fabrication d'autres réservoirs, citernes et conteneurs métalliques" + }, + { + "id": "25.29Z", + "label": "Fabrication d'autres réservoirs, citernes et conteneurs métalliques" + }, + { + "id": "25.3", + "label": "Fabrication de générateurs de vapeur, à l'exception des chaudières pour le chauffage central" + }, + { + "id": "25.30", + "label": "Fabrication de générateurs de vapeur, à l'exception des chaudières pour le chauffage central" + }, + { + "id": "25.30Z", + "label": "Fabrication de générateurs de vapeur, à l'exception des chaudières pour le chauffage central" + }, + { + "id": "25.4", + "label": "Fabrication d'armes et de munitions" + }, + { + "id": "25.40", + "label": "Fabrication d'armes et de munitions" + }, + { + "id": "25.40Z", + "label": "Fabrication d'armes et de munitions" + }, + { + "id": "25.5", + "label": "Forge, emboutissage, estampage ; métallurgie des poudres" + }, + { + "id": "25.50", + "label": "Forge, emboutissage, estampage ; métallurgie des poudres" + }, + { + "id": "25.50A", + "label": "Forge, estampage, matriçage ; métallurgie des poudres" + }, + { + "id": "25.50B", + "label": "Découpage, emboutissage" + }, + { + "id": "25.6", + "label": "Traitement et revêtement des métaux ; usinage" + }, + { + "id": "25.61", + "label": "Traitement et revêtement des métaux" + }, + { + "id": "25.61Z", + "label": "Traitement et revêtement des métaux" + }, + { + "id": "25.62", + "label": "Usinage" + }, + { + "id": "25.62A", + "label": "Décolletage" + }, + { + "id": "25.62B", + "label": "Mécanique industrielle" + }, + { + "id": "25.7", + "label": "Fabrication de coutellerie, d'outillage et de quincaillerie" + }, + { + "id": "25.71", + "label": "Fabrication de coutellerie" + }, + { + "id": "25.71Z", + "label": "Fabrication de coutellerie" + }, + { + "id": "25.72", + "label": "Fabrication de serrures et de ferrures" + }, + { + "id": "25.72Z", + "label": "Fabrication de serrures et de ferrures" + }, + { + "id": "25.73", + "label": "Fabrication d'outillage" + }, + { + "id": "25.73A", + "label": "Fabrication de moules et modèles" + }, + { + "id": "25.73B", + "label": "Fabrication d'autres outillages" + }, + { + "id": "25.9", + "label": "Fabrication d'autres ouvrages en métaux" + }, + { + "id": "25.91", + "label": "Fabrication de fûts et emballages métalliques similaires" + }, + { + "id": "25.91Z", + "label": "Fabrication de fûts et emballages métalliques similaires" + }, + { + "id": "25.92", + "label": "Fabrication d'emballages métalliques légers" + }, + { + "id": "25.92Z", + "label": "Fabrication d'emballages métalliques légers" + }, + { + "id": "25.93", + "label": "Fabrication d'articles en fils métalliques, de chaînes et de ressorts" + }, + { + "id": "25.93Z", + "label": "Fabrication d'articles en fils métalliques, de chaînes et de ressorts" + }, + { + "id": "25.94", + "label": "Fabrication de vis et de boulons" + }, + { + "id": "25.94Z", + "label": "Fabrication de vis et de boulons" + }, + { + "id": "25.99", + "label": "Fabrication d'autres produits métalliques n.c.a." + }, + { + "id": "25.99A", + "label": "Fabrication d'articles métalliques ménagers" + }, + { + "id": "25.99B", + "label": "Fabrication d'autres articles métalliques" + }, + { + "id": "26", + "label": "Fabrication de produits informatiques, électroniques et optiques" + }, + { + "id": "26.1", + "label": "Fabrication de composants et cartes électroniques" + }, + { + "id": "26.11", + "label": "Fabrication de composants électroniques" + }, + { + "id": "26.11Z", + "label": "Fabrication de composants électroniques" + }, + { + "id": "26.12", + "label": "Fabrication de cartes électroniques assemblées" + }, + { + "id": "26.12Z", + "label": "Fabrication de cartes électroniques assemblées" + }, + { + "id": "26.2", + "label": "Fabrication d'ordinateurs et d'équipements périphériques" + }, + { + "id": "26.20", + "label": "Fabrication d'ordinateurs et d'équipements périphériques" + }, + { + "id": "26.20Z", + "label": "Fabrication d'ordinateurs et d'équipements périphériques" + }, + { + "id": "26.3", + "label": "Fabrication d'équipements de communication" + }, + { + "id": "26.30", + "label": "Fabrication d'équipements de communication" + }, + { + "id": "26.30Z", + "label": "Fabrication d'équipements de communication" + }, + { + "id": "26.4", + "label": "Fabrication de produits électroniques grand public" + }, + { + "id": "26.40", + "label": "Fabrication de produits électroniques grand public" + }, + { + "id": "26.40Z", + "label": "Fabrication de produits électroniques grand public" + }, + { + "id": "26.5", + "label": "Fabrication d'instruments et d'appareils de mesure, d'essai et de navigation ; horlogerie" + }, + { + "id": "26.51", + "label": "Fabrication d'instruments et d'appareils de mesure, d'essai et de navigation" + }, + { + "id": "26.51A", + "label": "Fabrication d'équipements d'aide à la navigation" + }, + { + "id": "26.51B", + "label": "Fabrication d'instrumentation scientifique et technique" + }, + { + "id": "26.52", + "label": "Horlogerie" + }, + { + "id": "26.52Z", + "label": "Horlogerie" + }, + { + "id": "26.6", + "label": "Fabrication d'équipements d'irradiation médicale, d'équipements électromédicaux et électrothérapeutiques" + }, + { + "id": "26.60", + "label": "Fabrication d'équipements d'irradiation médicale, d'équipements électromédicaux et électrothérapeutiques" + }, + { + "id": "26.60Z", + "label": "Fabrication d'équipements d'irradiation médicale, d'équipements électromédicaux et électrothérapeutiques" + }, + { + "id": "26.7", + "label": "Fabrication de matériels optique et photographique" + }, + { + "id": "26.70", + "label": "Fabrication de matériels optique et photographique" + }, + { + "id": "26.70Z", + "label": "Fabrication de matériels optique et photographique" + }, + { + "id": "26.8", + "label": "Fabrication de supports magnétiques et optiques" + }, + { + "id": "26.80", + "label": "Fabrication de supports magnétiques et optiques" + }, + { + "id": "26.80Z", + "label": "Fabrication de supports magnétiques et optiques" + }, + { + "id": "27", + "label": "Fabrication d'équipements électriques" + }, + { + "id": "27.1", + "label": "Fabrication de moteurs, génératrices et transformateurs électriques et de matériel de distribution et de commande électrique" + }, + { + "id": "27.11", + "label": "Fabrication de moteurs, génératrices et transformateurs électriques" + }, + { + "id": "27.11Z", + "label": "Fabrication de moteurs, génératrices et transformateurs électriques" + }, + { + "id": "27.12", + "label": "Fabrication de matériel de distribution et de commande électrique" + }, + { + "id": "27.12Z", + "label": "Fabrication de matériel de distribution et de commande électrique" + }, + { + "id": "27.2", + "label": "Fabrication de piles et d'accumulateurs électriques" + }, + { + "id": "27.20", + "label": "Fabrication de piles et d'accumulateurs électriques" + }, + { + "id": "27.20Z", + "label": "Fabrication de piles et d'accumulateurs électriques" + }, + { + "id": "27.3", + "label": "Fabrication de fils et câbles et de matériel d'installation électrique" + }, + { + "id": "27.31", + "label": "Fabrication de câbles de fibres optiques" + }, + { + "id": "27.31Z", + "label": "Fabrication de câbles de fibres optiques" + }, + { + "id": "27.32", + "label": "Fabrication d'autres fils et câbles électroniques ou électriques" + }, + { + "id": "27.32Z", + "label": "Fabrication d'autres fils et câbles électroniques ou électriques" + }, + { + "id": "27.33", + "label": "Fabrication de matériel d'installation électrique" + }, + { + "id": "27.33Z", + "label": "Fabrication de matériel d'installation électrique" + }, + { + "id": "27.4", + "label": "Fabrication d'appareils d'éclairage électrique" + }, + { + "id": "27.40", + "label": "Fabrication d'appareils d'éclairage électrique" + }, + { + "id": "27.40Z", + "label": "Fabrication d'appareils d'éclairage électrique" + }, + { + "id": "27.5", + "label": "Fabrication d'appareils ménagers" + }, + { + "id": "27.51", + "label": "Fabrication d'appareils électroménagers" + }, + { + "id": "27.51Z", + "label": "Fabrication d'appareils électroménagers" + }, + { + "id": "27.52", + "label": "Fabrication d'appareils ménagers non électriques" + }, + { + "id": "27.52Z", + "label": "Fabrication d'appareils ménagers non électriques" + }, + { + "id": "27.9", + "label": "Fabrication d'autres matériels électriques" + }, + { + "id": "27.90", + "label": "Fabrication d'autres matériels électriques" + }, + { + "id": "27.90Z", + "label": "Fabrication d'autres matériels électriques" + }, + { + "id": "28", + "label": "Fabrication de machines et équipements n.c.a." + }, + { + "id": "28.1", + "label": "Fabrication de machines d'usage général" + }, + { + "id": "28.11", + "label": "Fabrication de moteurs et turbines, à l'exception des moteurs d’avions et de véhicules" + }, + { + "id": "28.11Z", + "label": "Fabrication de moteurs et turbines, à l'exception des moteurs d’avions et de véhicules" + }, + { + "id": "28.12", + "label": "Fabrication d'équipements hydrauliques et pneumatiques" + }, + { + "id": "28.12Z", + "label": "Fabrication d'équipements hydrauliques et pneumatiques" + }, + { + "id": "28.13", + "label": "Fabrication d'autres pompes et compresseurs" + }, + { + "id": "28.13Z", + "label": "Fabrication d'autres pompes et compresseurs" + }, + { + "id": "28.14", + "label": "Fabrication d'autres articles de robinetterie" + }, + { + "id": "28.14Z", + "label": "Fabrication d'autres articles de robinetterie" + }, + { + "id": "28.15", + "label": "Fabrication d'engrenages et d'organes mécaniques de transmission" + }, + { + "id": "28.15Z", + "label": "Fabrication d'engrenages et d'organes mécaniques de transmission" + }, + { + "id": "28.2", + "label": "Fabrication d'autres machines d'usage général" + }, + { + "id": "28.21", + "label": "Fabrication de fours et brûleurs" + }, + { + "id": "28.21Z", + "label": "Fabrication de fours et brûleurs" + }, + { + "id": "28.22", + "label": "Fabrication de matériel de levage et de manutention" + }, + { + "id": "28.22Z", + "label": "Fabrication de matériel de levage et de manutention" + }, + { + "id": "28.23", + "label": "Fabrication de machines et d'équipements de bureau (à l'exception des ordinateurs et équipements périphériques)" + }, + { + "id": "28.23Z", + "label": "Fabrication de machines et d'équipements de bureau (à l'exception des ordinateurs et équipements périphériques)" + }, + { + "id": "28.24", + "label": "Fabrication d'outillage portatif à moteur incorporé" + }, + { + "id": "28.24Z", + "label": "Fabrication d'outillage portatif à moteur incorporé" + }, + { + "id": "28.25", + "label": "Fabrication d'équipements aérauliques et frigorifiques industriels" + }, + { + "id": "28.25Z", + "label": "Fabrication d'équipements aérauliques et frigorifiques industriels" + }, + { + "id": "28.29", + "label": "Fabrication de machines diverses d'usage général" + }, + { + "id": "28.29A", + "label": "Fabrication d'équipements d'emballage, de conditionnement et de pesage" + }, + { + "id": "28.29B", + "label": "Fabrication d'autres machines d'usage général" + }, + { + "id": "28.3", + "label": "Fabrication de machines agricoles et forestières" + }, + { + "id": "28.30", + "label": "Fabrication de machines agricoles et forestières" + }, + { + "id": "28.30Z", + "label": "Fabrication de machines agricoles et forestières" + }, + { + "id": "28.4", + "label": "Fabrication de machines de formage des métaux et de machines-outils" + }, + { + "id": "28.41", + "label": "Fabrication de machines de formage des métaux" + }, + { + "id": "28.41Z", + "label": "Fabrication de machines-outils pour le travail des métaux" + }, + { + "id": "28.49", + "label": "Fabrication d'autres machines-outils" + }, + { + "id": "28.49Z", + "label": "Fabrication d'autres machines-outils" + }, + { + "id": "28.9", + "label": "Fabrication d'autres machines d'usage spécifique" + }, + { + "id": "28.91", + "label": "Fabrication de machines pour la métallurgie" + }, + { + "id": "28.91Z", + "label": "Fabrication de machines pour la métallurgie" + }, + { + "id": "28.92", + "label": "Fabrication de machines pour l'extraction ou la construction" + }, + { + "id": "28.92Z", + "label": "Fabrication de machines pour l'extraction ou la construction" + }, + { + "id": "28.93", + "label": "Fabrication de machines pour l'industrie agro-alimentaire" + }, + { + "id": "28.93Z", + "label": "Fabrication de machines pour l'industrie agro-alimentaire" + }, + { + "id": "28.94", + "label": "Fabrication de machines pour les industries textiles" + }, + { + "id": "28.94Z", + "label": "Fabrication de machines pour les industries textiles" + }, + { + "id": "28.95", + "label": "Fabrication de machines pour les industries du papier et du carton" + }, + { + "id": "28.95Z", + "label": "Fabrication de machines pour les industries du papier et du carton" + }, + { + "id": "28.96", + "label": "Fabrication de machines pour le travail du caoutchouc ou des plastiques" + }, + { + "id": "28.96Z", + "label": "Fabrication de machines pour le travail du caoutchouc ou des plastiques" + }, + { + "id": "28.99", + "label": "Fabrication d'autres machines d'usage spécifique n.c.a." + }, + { + "id": "28.99A", + "label": "Fabrication de machines d'imprimerie" + }, + { + "id": "28.99B", + "label": "Fabrication d'autres machines spécialisées" + }, + { + "id": "29", + "label": "Industrie automobile" + }, + { + "id": "29.1", + "label": "Construction de véhicules automobiles" + }, + { + "id": "29.10", + "label": "Construction de véhicules automobiles" + }, + { + "id": "29.10Z", + "label": "Construction de véhicules automobiles" + }, + { + "id": "29.2", + "label": "Fabrication de carrosseries et remorques" + }, + { + "id": "29.20", + "label": "Fabrication de carrosseries et remorques" + }, + { + "id": "29.20Z", + "label": "Fabrication de carrosseries et remorques" + }, + { + "id": "29.3", + "label": "Fabrication d'équipements automobiles" + }, + { + "id": "29.31", + "label": "Fabrication d'équipements électriques et électroniques automobiles" + }, + { + "id": "29.31Z", + "label": "Fabrication d'équipements électriques et électroniques automobiles" + }, + { + "id": "29.32", + "label": "Fabrication d'autres équipements automobiles" + }, + { + "id": "29.32Z", + "label": "Fabrication d'autres équipements automobiles" + }, + { + "id": "30", + "label": "Fabrication d'autres matériels de transport" + }, + { + "id": "30.1", + "label": "Construction navale" + }, + { + "id": "30.11", + "label": "Construction de navires et de structures flottantes" + }, + { + "id": "30.11Z", + "label": "Construction de navires et de structures flottantes" + }, + { + "id": "30.12", + "label": "Construction de bateaux de plaisance" + }, + { + "id": "30.12Z", + "label": "Construction de bateaux de plaisance" + }, + { + "id": "30.2", + "label": "Construction de locomotives et d'autre matériel ferroviaire roulant" + }, + { + "id": "30.20", + "label": "Construction de locomotives et d'autre matériel ferroviaire roulant" + }, + { + "id": "30.20Z", + "label": "Construction de locomotives et d'autre matériel ferroviaire roulant" + }, + { + "id": "30.3", + "label": "Construction aéronautique et spatiale" + }, + { + "id": "30.30", + "label": "Construction aéronautique et spatiale" + }, + { + "id": "30.30Z", + "label": "Construction aéronautique et spatiale" + }, + { + "id": "30.4", + "label": "Construction de véhicules militaires de combat" + }, + { + "id": "30.40", + "label": "Construction de véhicules militaires de combat" + }, + { + "id": "30.40Z", + "label": "Construction de véhicules militaires de combat" + }, + { + "id": "30.9", + "label": "Fabrication de matériels de transport n.c.a." + }, + { + "id": "30.91", + "label": "Fabrication de motocycles" + }, + { + "id": "30.91Z", + "label": "Fabrication de motocycles" + }, + { + "id": "30.92", + "label": "Fabrication de bicyclettes et de véhicules pour invalides" + }, + { + "id": "30.92Z", + "label": "Fabrication de bicyclettes et de véhicules pour invalides" + }, + { + "id": "30.99", + "label": "Fabrication d’autres équipements de transport n.c.a." + }, + { + "id": "30.99Z", + "label": "Fabrication d’autres équipements de transport n.c.a." + }, + { + "id": "31", + "label": "Fabrication de meubles" + }, + { + "id": "31.0", + "label": "Fabrication de meubles" + }, + { + "id": "31.01", + "label": "Fabrication de meubles de bureau et de magasin" + }, + { + "id": "31.01Z", + "label": "Fabrication de meubles de bureau et de magasin" + }, + { + "id": "31.02", + "label": "Fabrication de meubles de cuisine" + }, + { + "id": "31.02Z", + "label": "Fabrication de meubles de cuisine" + }, + { + "id": "31.03", + "label": "Fabrication de matelas" + }, + { + "id": "31.03Z", + "label": "Fabrication de matelas" + }, + { + "id": "31.09", + "label": "Fabrication d'autres meubles" + }, + { + "id": "31.09A", + "label": "Fabrication de sièges d'ameublement d'intérieur" + }, + { + "id": "31.09B", + "label": "Fabrication d’autres meubles et industries connexes de l’ameublement" + }, + { + "id": "32", + "label": "Autres industries manufacturières" + }, + { + "id": "32.1", + "label": "Fabrication d’articles de joaillerie, bijouterie et articles similaires" + }, + { + "id": "32.11", + "label": "Frappe de monnaie" + }, + { + "id": "32.11Z", + "label": "Frappe de monnaie" + }, + { + "id": "32.12", + "label": "Fabrication d’articles de joaillerie et bijouterie" + }, + { + "id": "32.12Z", + "label": "Fabrication d’articles de joaillerie et bijouterie" + }, + { + "id": "32.13", + "label": "Fabrication d’articles de bijouterie fantaisie et articles similaires" + }, + { + "id": "32.13Z", + "label": "Fabrication d’articles de bijouterie fantaisie et articles similaires" + }, + { + "id": "32.2", + "label": "Fabrication d'instruments de musique" + }, + { + "id": "32.20", + "label": "Fabrication d'instruments de musique" + }, + { + "id": "32.20Z", + "label": "Fabrication d'instruments de musique" + }, + { + "id": "32.3", + "label": "Fabrication d'articles de sport" + }, + { + "id": "32.30", + "label": "Fabrication d'articles de sport" + }, + { + "id": "32.30Z", + "label": "Fabrication d'articles de sport" + }, + { + "id": "32.4", + "label": "Fabrication de jeux et jouets" + }, + { + "id": "32.40", + "label": "Fabrication de jeux et jouets" + }, + { + "id": "32.40Z", + "label": "Fabrication de jeux et jouets" + }, + { + "id": "32.5", + "label": "Fabrication d'instruments et de fournitures à usage médical et dentaire" + }, + { + "id": "32.50", + "label": "Fabrication d'instruments et de fournitures à usage médical et dentaire" + }, + { + "id": "32.50A", + "label": "Fabrication de matériel médico-chirurgical et dentaire" + }, + { + "id": "32.50B", + "label": "Fabrication de lunettes" + }, + { + "id": "32.9", + "label": "Activités manufacturières n.c.a." + }, + { + "id": "32.91", + "label": "Fabrication d’articles de brosserie" + }, + { + "id": "32.91Z", + "label": "Fabrication d’articles de brosserie" + }, + { + "id": "32.99", + "label": "Autres activités manufacturières n.c.a." + }, + { + "id": "32.99Z", + "label": "Autres activités manufacturières n.c.a." + }, + { + "id": "33", + "label": "Réparation et installation de machines et d'équipements" + }, + { + "id": "33.1", + "label": "Réparation d'ouvrages en métaux, de machines et d'équipements" + }, + { + "id": "33.11", + "label": "Réparation d'ouvrages en métaux" + }, + { + "id": "33.11Z", + "label": "Réparation d'ouvrages en métaux" + }, + { + "id": "33.12", + "label": "Réparation de machines et équipements mécaniques" + }, + { + "id": "33.12Z", + "label": "Réparation de machines et équipements mécaniques" + }, + { + "id": "33.13", + "label": "Réparation de matériels électroniques et optiques" + }, + { + "id": "33.13Z", + "label": "Réparation de matériels électroniques et optiques" + }, + { + "id": "33.14", + "label": "Réparation d'équipements électriques" + }, + { + "id": "33.14Z", + "label": "Réparation d'équipements électriques" + }, + { + "id": "33.15", + "label": "Réparation et maintenance navale" + }, + { + "id": "33.15Z", + "label": "Réparation et maintenance navale" + }, + { + "id": "33.16", + "label": "Réparation et maintenance d'aéronefs et d'engins spatiaux" + }, + { + "id": "33.16Z", + "label": "Réparation et maintenance d'aéronefs et d'engins spatiaux" + }, + { + "id": "33.17", + "label": "Réparation et maintenance d'autres équipements de transport" + }, + { + "id": "33.17Z", + "label": "Réparation et maintenance d'autres équipements de transport" + }, + { + "id": "33.19", + "label": "Réparation d'autres équipements" + }, + { + "id": "33.19Z", + "label": "Réparation d'autres équipements" + }, + { + "id": "33.2", + "label": "Installation de machines et d'équipements industriels" + }, + { + "id": "33.20", + "label": "Installation de machines et d'équipements industriels" + }, + { + "id": "33.20A", + "label": "Installation de structures métalliques, chaudronnées et de tuyauterie" + }, + { + "id": "33.20B", + "label": "Installation de machines et équipements mécaniques" + }, + { + "id": "33.20C", + "label": "Conception d'ensemble et assemblage sur site industriel d'équipements de contrôle des processus industriels" + }, + { + "id": "33.20D", + "label": "Installation d'équipements électriques, de matériels électroniques et optiques ou d'autres matériels" + }, + { + "id": "35", + "label": "Production et distribution d'électricité, de gaz, de vapeur et d'air conditionné" + }, + { + "id": "35.1", + "label": "Production, transport et distribution d'électricité" + }, + { + "id": "35.11", + "label": "Production d'électricité" + }, + { + "id": "35.11Z", + "label": "Production d'électricité" + }, + { + "id": "35.12", + "label": "Transport d'électricité" + }, + { + "id": "35.12Z", + "label": "Transport d'électricité" + }, + { + "id": "35.13", + "label": "Distribution d'électricité" + }, + { + "id": "35.13Z", + "label": "Distribution d'électricité" + }, + { + "id": "35.14", + "label": "Commerce d'électricité" + }, + { + "id": "35.14Z", + "label": "Commerce d'électricité" + }, + { + "id": "35.2", + "label": "Production et distribution de combustibles gazeux" + }, + { + "id": "35.21", + "label": "Production de combustibles gazeux" + }, + { + "id": "35.21Z", + "label": "Production de combustibles gazeux" + }, + { + "id": "35.22", + "label": "Distribution de combustibles gazeux par conduites" + }, + { + "id": "35.22Z", + "label": "Distribution de combustibles gazeux par conduites" + }, + { + "id": "35.23", + "label": "Commerce de combustibles gazeux par conduites" + }, + { + "id": "35.23Z", + "label": "Commerce de combustibles gazeux par conduites" + }, + { + "id": "35.3", + "label": "Production et distribution de vapeur et d'air conditionné" + }, + { + "id": "35.30", + "label": "Production et distribution de vapeur et d'air conditionné" + }, + { + "id": "35.30Z", + "label": "Production et distribution de vapeur et d'air conditionné" + }, + { + "id": "36", + "label": "Captage, traitement et distribution d'eau" + }, + { + "id": "36.0", + "label": "Captage, traitement et distribution d'eau" + }, + { + "id": "36.00", + "label": "Captage, traitement et distribution d'eau" + }, + { + "id": "36.00Z", + "label": "Captage, traitement et distribution d'eau" + }, + { + "id": "37", + "label": "Collecte et traitement des eaux usées" + }, + { + "id": "37.0", + "label": "Collecte et traitement des eaux usées" + }, + { + "id": "37.00", + "label": "Collecte et traitement des eaux usées" + }, + { + "id": "37.00Z", + "label": "Collecte et traitement des eaux usées" + }, + { + "id": "38", + "label": "Collecte, traitement et élimination des déchets ; récupération" + }, + { + "id": "38.1", + "label": "Collecte des déchets" + }, + { + "id": "38.11", + "label": "Collecte des déchets non dangereux" + }, + { + "id": "38.11Z", + "label": "Collecte des déchets non dangereux" + }, + { + "id": "38.12", + "label": "Collecte des déchets dangereux" + }, + { + "id": "38.12Z", + "label": "Collecte des déchets dangereux" + }, + { + "id": "38.2", + "label": "Traitement et élimination des déchets" + }, + { + "id": "38.21", + "label": "Traitement et élimination des déchets non dangereux" + }, + { + "id": "38.21Z", + "label": "Traitement et élimination des déchets non dangereux" + }, + { + "id": "38.22", + "label": "Traitement et élimination des déchets dangereux" + }, + { + "id": "38.22Z", + "label": "Traitement et élimination des déchets dangereux" + }, + { + "id": "38.3", + "label": "Récupération" + }, + { + "id": "38.31", + "label": "Démantèlement d'épaves" + }, + { + "id": "38.31Z", + "label": "Démantèlement d'épaves" + }, + { + "id": "38.32", + "label": "Récupération de déchets triés" + }, + { + "id": "38.32Z", + "label": "Récupération de déchets triés" + }, + { + "id": "39", + "label": "Dépollution et autres services de gestion des déchets" + }, + { + "id": "39.0", + "label": "Dépollution et autres services de gestion des déchets" + }, + { + "id": "39.00", + "label": "Dépollution et autres services de gestion des déchets" + }, + { + "id": "39.00Z", + "label": "Dépollution et autres services de gestion des déchets" + }, + { + "id": "41", + "label": "Construction de bâtiments" + }, + { + "id": "41.1", + "label": "Promotion immobilière" + }, + { + "id": "41.10", + "label": "Promotion immobilière" + }, + { + "id": "41.10A", + "label": "Promotion immobilière de logements" + }, + { + "id": "41.10B", + "label": "Promotion immobilière de bureaux" + }, + { + "id": "41.10C", + "label": "Promotion immobilière d'autres bâtiments" + }, + { + "id": "41.10D", + "label": "Supports juridiques de programmes" + }, + { + "id": "41.2", + "label": "Construction de bâtiments résidentiels et non résidentiels" + }, + { + "id": "41.20", + "label": "Construction de bâtiments résidentiels et non résidentiels" + }, + { + "id": "41.20A", + "label": "Construction de maisons individuelles" + }, + { + "id": "41.20B", + "label": "Construction d'autres bâtiments" + }, + { + "id": "42", + "label": "Génie civil" + }, + { + "id": "42.1", + "label": "Construction de routes et de voies ferrées" + }, + { + "id": "42.11", + "label": "Construction de routes et autoroutes" + }, + { + "id": "42.11Z", + "label": "Construction de routes et autoroutes" + }, + { + "id": "42.12", + "label": "Construction de voies ferrées de surface et souterraines" + }, + { + "id": "42.12Z", + "label": "Construction de voies ferrées de surface et souterraines" + }, + { + "id": "42.13", + "label": "Construction de ponts et tunnels" + }, + { + "id": "42.13A", + "label": "Construction d'ouvrages d'art" + }, + { + "id": "42.13B", + "label": "Construction et entretien de tunnels" + }, + { + "id": "42.2", + "label": "Construction de réseaux et de lignes" + }, + { + "id": "42.21", + "label": "Construction de réseaux pour fluides" + }, + { + "id": "42.21Z", + "label": "Construction de réseaux pour fluides" + }, + { + "id": "42.22", + "label": "Construction de réseaux électriques et de télécommunications" + }, + { + "id": "42.22Z", + "label": "Construction de réseaux électriques et de télécommunications" + }, + { + "id": "42.9", + "label": "Construction d'autres ouvrages de génie civil" + }, + { + "id": "42.91", + "label": "Construction d'ouvrages maritimes et fluviaux" + }, + { + "id": "42.91Z", + "label": "Construction d'ouvrages maritimes et fluviaux" + }, + { + "id": "42.99", + "label": "Construction d'autres ouvrages de génie civil n.c.a." + }, + { + "id": "42.99Z", + "label": "Construction d'autres ouvrages de génie civil n.c.a." + }, + { + "id": "43", + "label": "Travaux de construction spécialisés" + }, + { + "id": "43.1", + "label": "Démolition et préparation des sites" + }, + { + "id": "43.11", + "label": "Travaux de démolition" + }, + { + "id": "43.11Z", + "label": "Travaux de démolition" + }, + { + "id": "43.12", + "label": "Travaux de préparation des sites" + }, + { + "id": "43.12A", + "label": "Travaux de terrassement courants et travaux préparatoires" + }, + { + "id": "43.12B", + "label": "Travaux de terrassement spécialisés ou de grande masse" + }, + { + "id": "43.13", + "label": "Forages et sondages" + }, + { + "id": "43.13Z", + "label": "Forages et sondages" + }, + { + "id": "43.2", + "label": "Travaux d'installation électrique, plomberie et autres travaux d'installation" + }, + { + "id": "43.21", + "label": "Installation électrique" + }, + { + "id": "43.21A", + "label": "Travaux d'installation électrique dans tous locaux" + }, + { + "id": "43.21B", + "label": "Travaux d'installation électrique sur la voie publique" + }, + { + "id": "43.22", + "label": "Travaux de plomberie et installation de chauffage et de conditionnement d'air" + }, + { + "id": "43.22A", + "label": "Travaux d'installation d'eau et de gaz en tous locaux" + }, + { + "id": "43.22B", + "label": "Travaux d'installation d'équipements thermiques et de climatisation" + }, + { + "id": "43.29", + "label": "Autres travaux d'installation" + }, + { + "id": "43.29A", + "label": "Travaux d'isolation" + }, + { + "id": "43.29B", + "label": "Autres travaux d'installation n.c.a." + }, + { + "id": "43.3", + "label": "Travaux de finition" + }, + { + "id": "43.31", + "label": "Travaux de plâtrerie" + }, + { + "id": "43.31Z", + "label": "Travaux de plâtrerie" + }, + { + "id": "43.32", + "label": "Travaux de menuiserie" + }, + { + "id": "43.32A", + "label": "Travaux de menuiserie bois et PVC" + }, + { + "id": "43.32B", + "label": "Travaux de menuiserie métallique et serrurerie" + }, + { + "id": "43.32C", + "label": "Agencement de lieux de vente" + }, + { + "id": "43.33", + "label": "Travaux de revêtement des sols et des murs" + }, + { + "id": "43.33Z", + "label": "Travaux de revêtement des sols et des murs" + }, + { + "id": "43.34", + "label": "Travaux de peinture et vitrerie" + }, + { + "id": "43.34Z", + "label": "Travaux de peinture et vitrerie" + }, + { + "id": "43.39", + "label": "Autres travaux de finition" + }, + { + "id": "43.39Z", + "label": "Autres travaux de finition" + }, + { + "id": "43.9", + "label": "Autres travaux de construction spécialisés" + }, + { + "id": "43.91", + "label": "Travaux de couverture" + }, + { + "id": "43.91A", + "label": "Travaux de charpente" + }, + { + "id": "43.91B", + "label": "Travaux de couverture par éléments" + }, + { + "id": "43.99", + "label": "Autres travaux de construction spécialisés n.c.a." + }, + { + "id": "43.99A", + "label": "Travaux d'étanchéification" + }, + { + "id": "43.99B", + "label": "Travaux de montage de structures métalliques" + }, + { + "id": "43.99C", + "label": "Travaux de maçonnerie générale et gros œuvre de bâtiment" + }, + { + "id": "43.99D", + "label": "Autres travaux spécialisés de construction" + }, + { + "id": "43.99E", + "label": "Location avec opérateur de matériel de construction" + }, + { + "id": "45", + "label": "Commerce et réparation d'automobiles et de motocycles" + }, + { + "id": "45.1", + "label": "Commerce de véhicules automobiles" + }, + { + "id": "45.11", + "label": "Commerce de voitures et de véhicules automobiles légers" + }, + { + "id": "45.11Z", + "label": "Commerce de voitures et de véhicules automobiles légers" + }, + { + "id": "45.19", + "label": "Commerce d'autres véhicules automobiles" + }, + { + "id": "45.19Z", + "label": "Commerce d'autres véhicules automobiles" + }, + { + "id": "45.2", + "label": "Entretien et réparation de véhicules automobiles" + }, + { + "id": "45.20", + "label": "Entretien et réparation de véhicules automobiles" + }, + { + "id": "45.20A", + "label": "Entretien et réparation de véhicules automobiles légers" + }, + { + "id": "45.20B", + "label": "Entretien et réparation d'autres véhicules automobiles" + }, + { + "id": "45.3", + "label": "Commerce d'équipements automobiles" + }, + { + "id": "45.31", + "label": "Commerce de gros d'équipements automobiles" + }, + { + "id": "45.31Z", + "label": "Commerce de gros d'équipements automobiles" + }, + { + "id": "45.32", + "label": "Commerce de détail d'équipements automobiles" + }, + { + "id": "45.32Z", + "label": "Commerce de détail d'équipements automobiles" + }, + { + "id": "45.4", + "label": "Commerce et réparation de motocycles" + }, + { + "id": "45.40", + "label": "Commerce et réparation de motocycles" + }, + { + "id": "45.40Z", + "label": "Commerce et réparation de motocycles" + }, + { + "id": "46", + "label": "Commerce de gros, à l’exception des automobiles et des motocycles" + }, + { + "id": "46.1", + "label": "Intermédiaires du commerce de gros" + }, + { + "id": "46.11", + "label": "Intermédiaires du commerce en matières premières agricoles, animaux vivants, matières premières textiles et produits semi-finis" + }, + { + "id": "46.11Z", + "label": "Intermédiaires du commerce en matières premières agricoles, animaux vivants, matières premières textiles et produits semi-finis" + }, + { + "id": "46.12", + "label": "Intermédiaires du commerce en combustibles, métaux, minéraux et produits chimiques" + }, + { + "id": "46.12A", + "label": "Centrales d'achat de carburant" + }, + { + "id": "46.12B", + "label": "Autres intermédiaires du commerce en combustibles, métaux, minéraux et produits chimiques" + }, + { + "id": "46.13", + "label": "Intermédiaires du commerce en bois et matériaux de construction" + }, + { + "id": "46.13Z", + "label": "Intermédiaires du commerce en bois et matériaux de construction" + }, + { + "id": "46.14", + "label": "Intermédiaires du commerce en machines, équipements industriels, navires et avions" + }, + { + "id": "46.14Z", + "label": "Intermédiaires du commerce en machines, équipements industriels, navires et avions" + }, + { + "id": "46.15", + "label": "Intermédiaires du commerce en meubles, articles de ménage et quincaillerie" + }, + { + "id": "46.15Z", + "label": "Intermédiaires du commerce en meubles, articles de ménage et quincaillerie" + }, + { + "id": "46.16", + "label": "Intermédiaires du commerce en textiles, habillement, fourrures, chaussures et articles en cuir" + }, + { + "id": "46.16Z", + "label": "Intermédiaires du commerce en textiles, habillement, fourrures, chaussures et articles en cuir" + }, + { + "id": "46.17", + "label": "Intermédiaires du commerce en denrées, boissons et tabac" + }, + { + "id": "46.17A", + "label": "Centrales d'achat alimentaires" + }, + { + "id": "46.17B", + "label": "Autres intermédiaires du commerce en denrées, boissons et tabac" + }, + { + "id": "46.18", + "label": "Intermédiaires spécialisés dans le commerce d'autres produits spécifiques" + }, + { + "id": "46.18Z", + "label": "Intermédiaires spécialisés dans le commerce d'autres produits spécifiques" + }, + { + "id": "46.19", + "label": "Intermédiaires du commerce en produits divers" + }, + { + "id": "46.19A", + "label": "Centrales d'achat non alimentaires" + }, + { + "id": "46.19B", + "label": "Autres intermédiaires du commerce en produits divers" + }, + { + "id": "46.2", + "label": "Commerce de gros de produits agricoles bruts et d'animaux vivants" + }, + { + "id": "46.21", + "label": "Commerce de gros de céréales, de tabac non manufacturé, de semences et d'aliments pour le bétail" + }, + { + "id": "46.21Z", + "label": "Commerce de gros (commerce interentreprises) de céréales, de tabac non manufacturé, de semences et d'aliments pour le bétail" + }, + { + "id": "46.22", + "label": "Commerce de gros de fleurs et plantes" + }, + { + "id": "46.22Z", + "label": "Commerce de gros (commerce interentreprises) de fleurs et plantes" + }, + { + "id": "46.23", + "label": "Commerce de gros d'animaux vivants" + }, + { + "id": "46.23Z", + "label": "Commerce de gros (commerce interentreprises) d'animaux vivants" + }, + { + "id": "46.24", + "label": "Commerce de gros de cuirs et peaux" + }, + { + "id": "46.24Z", + "label": "Commerce de gros (commerce interentreprises) de cuirs et peaux" + }, + { + "id": "46.3", + "label": "Commerce de gros de produits alimentaires, de boissons et de tabac" + }, + { + "id": "46.31", + "label": "Commerce de gros de fruits et légumes" + }, + { + "id": "46.31Z", + "label": "Commerce de gros (commerce interentreprises) de fruits et légumes" + }, + { + "id": "46.32", + "label": "Commerce de gros de viandes et de produits à base de viande" + }, + { + "id": "46.32A", + "label": "Commerce de gros (commerce interentreprises) de viandes de boucherie" + }, + { + "id": "46.32B", + "label": "Commerce de gros (commerce interentreprises) de produits à base de viande" + }, + { + "id": "46.32C", + "label": "Commerce de gros (commerce interentreprises) de volailles et gibier" + }, + { + "id": "46.33", + "label": "Commerce de gros de produits laitiers, œufs, huiles et matières grasses comestibles" + }, + { + "id": "46.33Z", + "label": "Commerce de gros (commerce interentreprises) de produits laitiers, œufs, huiles et matières grasses comestibles" + }, + { + "id": "46.34", + "label": "Commerce de gros de boissons" + }, + { + "id": "46.34Z", + "label": "Commerce de gros (commerce interentreprises) de boissons" + }, + { + "id": "46.35", + "label": "Commerce de gros de produits à base de tabac" + }, + { + "id": "46.35Z", + "label": "Commerce de gros (commerce interentreprises) de produits à base de tabac" + }, + { + "id": "46.36", + "label": "Commerce de gros de sucre, chocolat et confiserie" + }, + { + "id": "46.36Z", + "label": "Commerce de gros (commerce interentreprises) de sucre, chocolat et confiserie" + }, + { + "id": "46.37", + "label": "Commerce de gros de café, thé, cacao et épices" + }, + { + "id": "46.37Z", + "label": "Commerce de gros (commerce interentreprises) de café, thé, cacao et épices" + }, + { + "id": "46.38", + "label": "Commerce de gros d'autres produits alimentaires, y compris poissons, crustacés et mollusques" + }, + { + "id": "46.38A", + "label": "Commerce de gros (commerce interentreprises) de poissons, crustacés et mollusques" + }, + { + "id": "46.38B", + "label": "Commerce de gros (commerce interentreprises) alimentaire spécialisé divers" + }, + { + "id": "46.39", + "label": "Commerce de gros non spécialisé de denrées, boissons et tabac" + }, + { + "id": "46.39A", + "label": "Commerce de gros (commerce interentreprises) de produits surgelés" + }, + { + "id": "46.39B", + "label": "Commerce de gros (commerce interentreprises) alimentaire non spécialisé" + }, + { + "id": "46.4", + "label": "Commerce de gros de biens domestiques" + }, + { + "id": "46.41", + "label": "Commerce de gros de textiles" + }, + { + "id": "46.41Z", + "label": "Commerce de gros (commerce interentreprises) de textiles" + }, + { + "id": "46.42", + "label": "Commerce de gros d'habillement et de chaussures" + }, + { + "id": "46.42Z", + "label": "Commerce de gros (commerce interentreprises) d'habillement et de chaussures" + }, + { + "id": "46.43", + "label": "Commerce de gros d'appareils électroménagers" + }, + { + "id": "46.43Z", + "label": "Commerce de gros (commerce interentreprises) d'appareils électroménagers" + }, + { + "id": "46.44", + "label": "Commerce de gros de vaisselle, verrerie et produits d'entretien" + }, + { + "id": "46.44Z", + "label": "Commerce de gros (commerce interentreprises) de vaisselle, verrerie et produits d'entretien" + }, + { + "id": "46.45", + "label": "Commerce de gros de parfumerie et de produits de beauté" + }, + { + "id": "46.45Z", + "label": "Commerce de gros (commerce interentreprises) de parfumerie et de produits de beauté" + }, + { + "id": "46.46", + "label": "Commerce de gros de produits pharmaceutiques" + }, + { + "id": "46.46Z", + "label": "Commerce de gros (commerce interentreprises) de produits pharmaceutiques" + }, + { + "id": "46.47", + "label": "Commerce de gros de meubles, de tapis et d'appareils d'éclairage" + }, + { + "id": "46.47Z", + "label": "Commerce de gros (commerce interentreprises) de meubles, de tapis et d'appareils d'éclairage" + }, + { + "id": "46.48", + "label": "Commerce de gros d'articles d'horlogerie et de bijouterie" + }, + { + "id": "46.48Z", + "label": "Commerce de gros (commerce interentreprises) d'articles d'horlogerie et de bijouterie" + }, + { + "id": "46.49", + "label": "Commerce de gros d'autres biens domestiques" + }, + { + "id": "46.49Z", + "label": "Commerce de gros (commerce interentreprises) d'autres biens domestiques" + }, + { + "id": "46.5", + "label": "Commerce de gros d'équipements de l'information et de la communication" + }, + { + "id": "46.51", + "label": "Commerce de gros d'ordinateurs, d'équipements informatiques périphériques et de logiciels" + }, + { + "id": "46.51Z", + "label": "Commerce de gros (commerce interentreprises) d'ordinateurs, d'équipements informatiques périphériques et de logiciels" + }, + { + "id": "46.52", + "label": "Commerce de gros de composants et d'équipements électroniques et de télécommunication" + }, + { + "id": "46.52Z", + "label": "Commerce de gros (commerce interentreprises) de composants et d'équipements électroniques et de télécommunication" + }, + { + "id": "46.6", + "label": "Commerce de gros d'autres équipements industriels" + }, + { + "id": "46.61", + "label": "Commerce de gros de matériel agricole" + }, + { + "id": "46.61Z", + "label": "Commerce de gros (commerce interentreprises) de matériel agricole" + }, + { + "id": "46.62", + "label": "Commerce de gros de machines-outils" + }, + { + "id": "46.62Z", + "label": "Commerce de gros (commerce interentreprises) de machines-outils" + }, + { + "id": "46.63", + "label": "Commerce de gros de machines pour l'extraction, la construction et le génie civil" + }, + { + "id": "46.63Z", + "label": "Commerce de gros (commerce interentreprises) de machines pour l'extraction, la construction et le génie civil" + }, + { + "id": "46.64", + "label": "Commerce de gros de machines pour l'industrie textile et l'habillement" + }, + { + "id": "46.64Z", + "label": "Commerce de gros (commerce interentreprises) de machines pour l'industrie textile et l'habillement" + }, + { + "id": "46.65", + "label": "Commerce de gros de mobilier de bureau" + }, + { + "id": "46.65Z", + "label": "Commerce de gros (commerce interentreprises) de mobilier de bureau" + }, + { + "id": "46.66", + "label": "Commerce de gros d'autres machines et équipements de bureau" + }, + { + "id": "46.66Z", + "label": "Commerce de gros (commerce interentreprises) d'autres machines et équipements de bureau" + }, + { + "id": "46.69", + "label": "Commerce de gros d'autres machines et équipements" + }, + { + "id": "46.69A", + "label": "Commerce de gros (commerce interentreprises) de matériel électrique" + }, + { + "id": "46.69B", + "label": "Commerce de gros (commerce interentreprises) de fournitures et équipements industriels divers" + }, + { + "id": "46.69C", + "label": "Commerce de gros (commerce interentreprises) de fournitures et équipements divers pour le commerce et les services" + }, + { + "id": "46.7", + "label": "Autres commerces de gros spécialisés" + }, + { + "id": "46.71", + "label": "Commerce de gros de combustibles et de produits annexes" + }, + { + "id": "46.71Z", + "label": "Commerce de gros (commerce interentreprises) de combustibles et de produits annexes" + }, + { + "id": "46.72", + "label": "Commerce de gros de minerais et métaux" + }, + { + "id": "46.72Z", + "label": "Commerce de gros (commerce interentreprises) de minerais et métaux" + }, + { + "id": "46.73", + "label": "Commerce de gros de bois, de matériaux de construction et d'appareils sanitaires" + }, + { + "id": "46.73A", + "label": "Commerce de gros (commerce interentreprises) de bois et de matériaux de construction" + }, + { + "id": "46.73B", + "label": "Commerce de gros (commerce interentreprises) d'appareils sanitaires et de produits de décoration" + }, + { + "id": "46.74", + "label": "Commerce de gros de quincaillerie et fournitures pour plomberie et chauffage" + }, + { + "id": "46.74A", + "label": "Commerce de gros (commerce interentreprises) de quincaillerie" + }, + { + "id": "46.74B", + "label": "Commerce de gros (commerce interentreprises) de fournitures pour la plomberie et le chauffage" + }, + { + "id": "46.75", + "label": "Commerce de gros de produits chimiques" + }, + { + "id": "46.75Z", + "label": "Commerce de gros (commerce interentreprises) de produits chimiques" + }, + { + "id": "46.76", + "label": "Commerce de gros d'autres produits intermédiaires" + }, + { + "id": "46.76Z", + "label": "Commerce de gros (commerce interentreprises) d'autres produits intermédiaires" + }, + { + "id": "46.77", + "label": "Commerce de gros de déchets et débris" + }, + { + "id": "46.77Z", + "label": "Commerce de gros (commerce interentreprises) de déchets et débris" + }, + { + "id": "46.9", + "label": "Commerce de gros non spécialisé" + }, + { + "id": "46.90", + "label": "Commerce de gros non spécialisé" + }, + { + "id": "46.90Z", + "label": "Commerce de gros (commerce interentreprises) non spécialisé" + }, + { + "id": "47", + "label": "Commerce de détail, à l’exception des automobiles et des motocycles" + }, + { + "id": "47.1", + "label": "Commerce de détail en magasin non spécialisé" + }, + { + "id": "47.11", + "label": "Commerce de détail en magasin non spécialisé à prédominance alimentaire" + }, + { + "id": "47.11A", + "label": "Commerce de détail de produits surgelés" + }, + { + "id": "47.11B", + "label": "Commerce d'alimentation générale" + }, + { + "id": "47.11C", + "label": "Supérettes" + }, + { + "id": "47.11D", + "label": "Supermarchés" + }, + { + "id": "47.11E", + "label": "Magasins multi-commerces" + }, + { + "id": "47.11F", + "label": "Hypermarchés" + }, + { + "id": "47.19", + "label": "Autre commerce de détail en magasin non spécialisé" + }, + { + "id": "47.19A", + "label": "Grands magasins" + }, + { + "id": "47.19B", + "label": "Autres commerces de détail en magasin non spécialisé" + }, + { + "id": "47.2", + "label": "Commerce de détail alimentaire en magasin spécialisé" + }, + { + "id": "47.21", + "label": "Commerce de détail de fruits et légumes en magasin spécialisé" + }, + { + "id": "47.21Z", + "label": "Commerce de détail de fruits et légumes en magasin spécialisé" + }, + { + "id": "47.22", + "label": "Commerce de détail de viandes et de produits à base de viande en magasin spécialisé" + }, + { + "id": "47.22Z", + "label": "Commerce de détail de viandes et de produits à base de viande en magasin spécialisé" + }, + { + "id": "47.23", + "label": "Commerce de détail de poissons, crustacés et mollusques en magasin spécialisé" + }, + { + "id": "47.23Z", + "label": "Commerce de détail de poissons, crustacés et mollusques en magasin spécialisé" + }, + { + "id": "47.24", + "label": "Commerce de détail de pain, pâtisserie et confiserie en magasin spécialisé" + }, + { + "id": "47.24Z", + "label": "Commerce de détail de pain, pâtisserie et confiserie en magasin spécialisé" + }, + { + "id": "47.25", + "label": "Commerce de détail de boissons en magasin spécialisé" + }, + { + "id": "47.25Z", + "label": "Commerce de détail de boissons en magasin spécialisé" + }, + { + "id": "47.26", + "label": "Commerce de détail de produits à base de tabac en magasin spécialisé" + }, + { + "id": "47.26Z", + "label": "Commerce de détail de produits à base de tabac en magasin spécialisé" + }, + { + "id": "47.29", + "label": "Autres commerces de détail alimentaires en magasin spécialisé" + }, + { + "id": "47.29Z", + "label": "Autres commerces de détail alimentaires en magasin spécialisé" + }, + { + "id": "47.3", + "label": "Commerce de détail de carburants en magasin spécialisé" + }, + { + "id": "47.30", + "label": "Commerce de détail de carburants en magasin spécialisé" + }, + { + "id": "47.30Z", + "label": "Commerce de détail de carburants en magasin spécialisé" + }, + { + "id": "47.4", + "label": "Commerce de détail d'équipements de l'information et de la communication en magasin spécialisé" + }, + { + "id": "47.41", + "label": "Commerce de détail d'ordinateurs, d'unités périphériques et de logiciels en magasin spécialisé" + }, + { + "id": "47.41Z", + "label": "Commerce de détail d'ordinateurs, d'unités périphériques et de logiciels en magasin spécialisé" + }, + { + "id": "47.42", + "label": "Commerce de détail de matériels de télécommunication en magasin spécialisé" + }, + { + "id": "47.42Z", + "label": "Commerce de détail de matériels de télécommunication en magasin spécialisé" + }, + { + "id": "47.43", + "label": "Commerce de détail de matériels audio/vidéo en magasin spécialisé" + }, + { + "id": "47.43Z", + "label": "Commerce de détail de matériels audio et vidéo en magasin spécialisé" + }, + { + "id": "47.5", + "label": "Commerce de détail d'autres équipements du foyer en magasin spécialisé" + }, + { + "id": "47.51", + "label": "Commerce de détail de textiles en magasin spécialisé" + }, + { + "id": "47.51Z", + "label": "Commerce de détail de textiles en magasin spécialisé" + }, + { + "id": "47.52", + "label": "Commerce de détail de quincaillerie, peintures et verres en magasin spécialisé" + }, + { + "id": "47.52A", + "label": "Commerce de détail de quincaillerie, peintures et verres en petites surfaces (moins de 400 m2)" + }, + { + "id": "47.52B", + "label": "Commerce de détail de quincaillerie, peintures et verres en grandes surfaces (400 m2et plus)" + }, + { + "id": "47.53", + "label": "Commerce de détail de tapis, moquettes et revêtements de murs et de sols en magasin spécialisé" + }, + { + "id": "47.53Z", + "label": "Commerce de détail de tapis, moquettes et revêtements de murs et de sols en magasin spécialisé" + }, + { + "id": "47.54", + "label": "Commerce de détail d'appareils électroménagers en magasin spécialisé" + }, + { + "id": "47.54Z", + "label": "Commerce de détail d'appareils électroménagers en magasin spécialisé" + }, + { + "id": "47.59", + "label": "Commerce de détail de meubles, appareils d'éclairage et autres articles de ménage en magasin spécialisé" + }, + { + "id": "47.59A", + "label": "Commerce de détail de meubles" + }, + { + "id": "47.59B", + "label": "Commerce de détail d'autres équipements du foyer" + }, + { + "id": "47.6", + "label": "Commerce de détail de biens culturels et de loisirs en magasin spécialisé" + }, + { + "id": "47.61", + "label": "Commerce de détail de livres en magasin spécialisé" + }, + { + "id": "47.61Z", + "label": "Commerce de détail de livres en magasin spécialisé" + }, + { + "id": "47.62", + "label": "Commerce de détail de journaux et papeterie en magasin spécialisé" + }, + { + "id": "47.62Z", + "label": "Commerce de détail de journaux et papeterie en magasin spécialisé" + }, + { + "id": "47.63", + "label": "Commerce de détail d'enregistrements musicaux et vidéo en magasin spécialisé" + }, + { + "id": "47.63Z", + "label": "Commerce de détail d'enregistrements musicaux et vidéo en magasin spécialisé" + }, + { + "id": "47.64", + "label": "Commerce de détail d'articles de sport en magasin spécialisé" + }, + { + "id": "47.64Z", + "label": "Commerce de détail d'articles de sport en magasin spécialisé" + }, + { + "id": "47.65", + "label": "Commerce de détail de jeux et jouets en magasin spécialisé" + }, + { + "id": "47.65Z", + "label": "Commerce de détail de jeux et jouets en magasin spécialisé" + }, + { + "id": "47.7", + "label": "Autres commerces de détail en magasin spécialisé" + }, + { + "id": "47.71", + "label": "Commerce de détail d'habillement en magasin spécialisé" + }, + { + "id": "47.71Z", + "label": "Commerce de détail d'habillement en magasin spécialisé" + }, + { + "id": "47.72", + "label": "Commerce de détail de chaussures et d'articles en cuir en magasin spécialisé" + }, + { + "id": "47.72A", + "label": "Commerce de détail de la chaussure" + }, + { + "id": "47.72B", + "label": "Commerce de détail de maroquinerie et d'articles de voyage" + }, + { + "id": "47.73", + "label": "Commerce de détail de produits pharmaceutiques en magasin spécialisé" + }, + { + "id": "47.73Z", + "label": "Commerce de détail de produits pharmaceutiques en magasin spécialisé" + }, + { + "id": "47.74", + "label": "Commerce de détail d'articles médicaux et orthopédiques en magasin spécialisé" + }, + { + "id": "47.74Z", + "label": "Commerce de détail d'articles médicaux et orthopédiques en magasin spécialisé" + }, + { + "id": "47.75", + "label": "Commerce de détail de parfumerie et de produits de beauté en magasin spécialisé" + }, + { + "id": "47.75Z", + "label": "Commerce de détail de parfumerie et de produits de beauté en magasin spécialisé" + }, + { + "id": "47.76", + "label": "Commerce de détail de fleurs, plantes, graines, engrais, animaux de compagnie et aliments pour ces animaux en magasin spécialisé" + }, + { + "id": "47.76Z", + "label": "Commerce de détail de fleurs, plantes, graines, engrais, animaux de compagnie et aliments pour ces animaux en magasin spécialisé" + }, + { + "id": "47.77", + "label": "Commerce de détail d'articles d'horlogerie et de bijouterie en magasin spécialisé" + }, + { + "id": "47.77Z", + "label": "Commerce de détail d'articles d'horlogerie et de bijouterie en magasin spécialisé" + }, + { + "id": "47.78", + "label": "Autre commerce de détail de biens neufs en magasin spécialisé" + }, + { + "id": "47.78A", + "label": "Commerces de détail d'optique" + }, + { + "id": "47.78B", + "label": "Commerces de détail de charbons et combustibles" + }, + { + "id": "47.78C", + "label": "Autres commerces de détail spécialisés divers" + }, + { + "id": "47.79", + "label": "Commerce de détail de biens d'occasion en magasin" + }, + { + "id": "47.79Z", + "label": "Commerce de détail de biens d'occasion en magasin" + }, + { + "id": "47.8", + "label": "Commerce de détail sur éventaires et marchés" + }, + { + "id": "47.81", + "label": "Commerce de détail alimentaire sur éventaires et marchés" + }, + { + "id": "47.81Z", + "label": "Commerce de détail alimentaire sur éventaires et marchés" + }, + { + "id": "47.82", + "label": "Commerce de détail de textiles, d'habillement et de chaussures sur éventaires et marchés" + }, + { + "id": "47.82Z", + "label": "Commerce de détail de textiles, d'habillement et de chaussures sur éventaires et marchés" + }, + { + "id": "47.89", + "label": "Autres commerces de détail sur éventaires et marchés" + }, + { + "id": "47.89Z", + "label": "Autres commerces de détail sur éventaires et marchés" + }, + { + "id": "47.9", + "label": "Commerce de détail hors magasin, éventaires ou marchés" + }, + { + "id": "47.91", + "label": "Vente à distance" + }, + { + "id": "47.91A", + "label": "Vente à distance sur catalogue général" + }, + { + "id": "47.91B", + "label": "Vente à distance sur catalogue spécialisé" + }, + { + "id": "47.99", + "label": "Autres commerces de détail hors magasin, éventaires ou marchés" + }, + { + "id": "47.99A", + "label": "Vente à domicile" + }, + { + "id": "47.99B", + "label": "Vente par automates et autres commerces de détail hors magasin, éventaires ou marchés n.c.a." + }, + { + "id": "49", + "label": "Transports terrestres et transport par conduites" + }, + { + "id": "49.1", + "label": "Transport ferroviaire interurbain de voyageurs" + }, + { + "id": "49.10", + "label": "Transport ferroviaire interurbain de voyageurs" + }, + { + "id": "49.10Z", + "label": "Transport ferroviaire interurbain de voyageurs" + }, + { + "id": "49.2", + "label": "Transports ferroviaires de fret" + }, + { + "id": "49.20", + "label": "Transports ferroviaires de fret" + }, + { + "id": "49.20Z", + "label": "Transports ferroviaires de fret" + }, + { + "id": "49.3", + "label": "Autres transports terrestres de voyageurs" + }, + { + "id": "49.31", + "label": "Transports urbains et suburbains de voyageurs" + }, + { + "id": "49.31Z", + "label": "Transports urbains et suburbains de voyageurs" + }, + { + "id": "49.32", + "label": "Transports de voyageurs par taxis" + }, + { + "id": "49.32Z", + "label": "Transports de voyageurs par taxis" + }, + { + "id": "49.39", + "label": "Autres transports terrestres de voyageurs n.c.a." + }, + { + "id": "49.39A", + "label": "Transports routiers réguliers de voyageurs" + }, + { + "id": "49.39B", + "label": "Autres transports routiers de voyageurs" + }, + { + "id": "49.39C", + "label": "Téléphériques et remontées mécaniques" + }, + { + "id": "49.4", + "label": "Transports routiers de fret et services de déménagement" + }, + { + "id": "49.41", + "label": "Transports routiers de fret" + }, + { + "id": "49.41A", + "label": "Transports routiers de fret interurbains" + }, + { + "id": "49.41B", + "label": "Transports routiers de fret de proximité" + }, + { + "id": "49.41C", + "label": "Location de camions avec chauffeur" + }, + { + "id": "49.42", + "label": "Services de déménagement" + }, + { + "id": "49.42Z", + "label": "Services de déménagement" + }, + { + "id": "49.5", + "label": "Transports par conduites" + }, + { + "id": "49.50", + "label": "Transports par conduites" + }, + { + "id": "49.50Z", + "label": "Transports par conduites" + }, + { + "id": "50", + "label": "Transports par eau" + }, + { + "id": "50.1", + "label": "Transports maritimes et côtiers de passagers" + }, + { + "id": "50.10", + "label": "Transports maritimes et côtiers de passagers" + }, + { + "id": "50.10Z", + "label": "Transports maritimes et côtiers de passagers" + }, + { + "id": "50.2", + "label": "Transports maritimes et côtiers de fret" + }, + { + "id": "50.20", + "label": "Transports maritimes et côtiers de fret" + }, + { + "id": "50.20Z", + "label": "Transports maritimes et côtiers de fret" + }, + { + "id": "50.3", + "label": "Transports fluviaux de passagers" + }, + { + "id": "50.30", + "label": "Transports fluviaux de passagers" + }, + { + "id": "50.30Z", + "label": "Transports fluviaux de passagers" + }, + { + "id": "50.4", + "label": "Transports fluviaux de fret" + }, + { + "id": "50.40", + "label": "Transports fluviaux de fret" + }, + { + "id": "50.40Z", + "label": "Transports fluviaux de fret" + }, + { + "id": "51", + "label": "Transports aériens" + }, + { + "id": "51.1", + "label": "Transports aériens de passagers" + }, + { + "id": "51.10", + "label": "Transports aériens de passagers" + }, + { + "id": "51.10Z", + "label": "Transports aériens de passagers" + }, + { + "id": "51.2", + "label": "Transports aériens de fret et transports spatiaux" + }, + { + "id": "51.21", + "label": "Transports aériens de fret" + }, + { + "id": "51.21Z", + "label": "Transports aériens de fret" + }, + { + "id": "51.22", + "label": "Transports spatiaux" + }, + { + "id": "51.22Z", + "label": "Transports spatiaux" + }, + { + "id": "52", + "label": "Entreposage et services auxiliaires des transports" + }, + { + "id": "52.1", + "label": "Entreposage et stockage" + }, + { + "id": "52.10", + "label": "Entreposage et stockage" + }, + { + "id": "52.10A", + "label": "Entreposage et stockage frigorifique" + }, + { + "id": "52.10B", + "label": "Entreposage et stockage non frigorifique" + }, + { + "id": "52.2", + "label": "Services auxiliaires des transports" + }, + { + "id": "52.21", + "label": "Services auxiliaires des transports terrestres" + }, + { + "id": "52.21Z", + "label": "Services auxiliaires des transports terrestres" + }, + { + "id": "52.22", + "label": "Services auxiliaires des transports par eau" + }, + { + "id": "52.22Z", + "label": "Services auxiliaires des transports par eau" + }, + { + "id": "52.23", + "label": "Services auxiliaires des transports aériens" + }, + { + "id": "52.23Z", + "label": "Services auxiliaires des transports aériens" + }, + { + "id": "52.24", + "label": "Manutention" + }, + { + "id": "52.24A", + "label": "Manutention portuaire" + }, + { + "id": "52.24B", + "label": "Manutention non portuaire" + }, + { + "id": "52.29", + "label": "Autres services auxiliaires des transports" + }, + { + "id": "52.29A", + "label": "Messagerie, fret express" + }, + { + "id": "52.29B", + "label": "Affrètement et organisation des transports" + }, + { + "id": "53", + "label": "Activités de poste et de courrier" + }, + { + "id": "53.1", + "label": "Activités de poste dans le cadre d'une obligation de service universel" + }, + { + "id": "53.10", + "label": "Activités de poste dans le cadre d'une obligation de service universel" + }, + { + "id": "53.10Z", + "label": "Activités de poste dans le cadre d'une obligation de service universel" + }, + { + "id": "53.2", + "label": "Autres activités de poste et de courrier" + }, + { + "id": "53.20", + "label": "Autres activités de poste et de courrier" + }, + { + "id": "53.20Z", + "label": "Autres activités de poste et de courrier" + }, + { + "id": "55", + "label": "Hébergement" + }, + { + "id": "55.1", + "label": "Hôtels et hébergement similaire" + }, + { + "id": "55.10", + "label": "Hôtels et hébergement similaire" + }, + { + "id": "55.10Z", + "label": "Hôtels et hébergement similaire" + }, + { + "id": "55.2", + "label": "Hébergement touristique et autre hébergement de courte durée" + }, + { + "id": "55.20", + "label": "Hébergement touristique et autre hébergement de courte durée" + }, + { + "id": "55.20Z", + "label": "Hébergement touristique et autre hébergement de courte durée" + }, + { + "id": "55.3", + "label": "Terrains de camping et parcs pour caravanes ou véhicules de loisirs" + }, + { + "id": "55.30", + "label": "Terrains de camping et parcs pour caravanes ou véhicules de loisirs" + }, + { + "id": "55.30Z", + "label": "Terrains de camping et parcs pour caravanes ou véhicules de loisirs" + }, + { + "id": "55.9", + "label": "Autres hébergements" + }, + { + "id": "55.90", + "label": "Autres hébergements" + }, + { + "id": "55.90Z", + "label": "Autres hébergements" + }, + { + "id": "56", + "label": "Restauration" + }, + { + "id": "56.1", + "label": "Restaurants et services de restauration mobile" + }, + { + "id": "56.10", + "label": "Restaurants et services de restauration mobile" + }, + { + "id": "56.10A", + "label": "Restauration traditionnelle" + }, + { + "id": "56.10B", + "label": "Cafétérias et autres libres-services" + }, + { + "id": "56.10C", + "label": "Restauration de type rapide" + }, + { + "id": "56.2", + "label": "Traiteurs et autres services de restauration" + }, + { + "id": "56.21", + "label": "Services des traiteurs" + }, + { + "id": "56.21Z", + "label": "Services des traiteurs" + }, + { + "id": "56.29", + "label": "Autres services de restauration" + }, + { + "id": "56.29A", + "label": "Restauration collective sous contrat" + }, + { + "id": "56.29B", + "label": "Autres services de restauration n.c.a." + }, + { + "id": "56.3", + "label": "Débits de boissons" + }, + { + "id": "56.30", + "label": "Débits de boissons" + }, + { + "id": "56.30Z", + "label": "Débits de boissons" + }, + { + "id": "58", + "label": "Édition" + }, + { + "id": "58.1", + "label": "Édition de livres et périodiques et autres activités d'édition" + }, + { + "id": "58.11", + "label": "Édition de livres" + }, + { + "id": "58.11Z", + "label": "Édition de livres" + }, + { + "id": "58.12", + "label": "Édition de répertoires et de fichiers d'adresses" + }, + { + "id": "58.12Z", + "label": "Édition de répertoires et de fichiers d'adresses" + }, + { + "id": "58.13", + "label": "Édition de journaux" + }, + { + "id": "58.13Z", + "label": "Édition de journaux" + }, + { + "id": "58.14", + "label": "Édition de revues et périodiques" + }, + { + "id": "58.14Z", + "label": "Édition de revues et périodiques" + }, + { + "id": "58.19", + "label": "Autres activités d'édition" + }, + { + "id": "58.19Z", + "label": "Autres activités d'édition" + }, + { + "id": "58.2", + "label": "Édition de logiciels" + }, + { + "id": "58.21", + "label": "Édition de jeux électroniques" + }, + { + "id": "58.21Z", + "label": "Édition de jeux électroniques" + }, + { + "id": "58.29", + "label": "Édition d'autres logiciels" + }, + { + "id": "58.29A", + "label": "Édition de logiciels système et de réseau" + }, + { + "id": "58.29B", + "label": "Edition de logiciels outils de développement et de langages" + }, + { + "id": "58.29C", + "label": "Edition de logiciels applicatifs" + }, + { + "id": "59", + "label": "Production de films cinématographiques, de vidéo et de programmes de télévision ; enregistrement sonore et édition musicale" + }, + { + "id": "59.1", + "label": "Activités cinématographiques, vidéo et de télévision" + }, + { + "id": "59.11", + "label": "Production de films cinématographiques, de vidéo et de programmes de télévision" + }, + { + "id": "59.11A", + "label": "Production de films et de programmes pour la télévision" + }, + { + "id": "59.11B", + "label": "Production de films institutionnels et publicitaires" + }, + { + "id": "59.11C", + "label": "Production de films pour le cinéma" + }, + { + "id": "59.12", + "label": "Post-production de films cinématographiques, de vidéo et de programmes de télévision" + }, + { + "id": "59.12Z", + "label": "Post-production de films cinématographiques, de vidéo et de programmes de télévision" + }, + { + "id": "59.13", + "label": "Distribution de films cinématographiques, de vidéo et de programmes de télévision" + }, + { + "id": "59.13A", + "label": "Distribution de films cinématographiques" + }, + { + "id": "59.13B", + "label": "Edition et distribution vidéo" + }, + { + "id": "59.14", + "label": "Projection de films cinématographiques" + }, + { + "id": "59.14Z", + "label": "Projection de films cinématographiques" + }, + { + "id": "59.2", + "label": "Enregistrement sonore et édition musicale" + }, + { + "id": "59.20", + "label": "Enregistrement sonore et édition musicale" + }, + { + "id": "59.20Z", + "label": "Enregistrement sonore et édition musicale" + }, + { + "id": "60", + "label": "Programmation et diffusion" + }, + { + "id": "60.1", + "label": "Édition et diffusion de programmes radio" + }, + { + "id": "60.10", + "label": "Édition et diffusion de programmes radio" + }, + { + "id": "60.10Z", + "label": "Édition et diffusion de programmes radio" + }, + { + "id": "60.2", + "label": "Programmation de télévision et télédiffusion" + }, + { + "id": "60.20", + "label": "Programmation de télévision et télédiffusion" + }, + { + "id": "60.20A", + "label": "Edition de chaînes généralistes" + }, + { + "id": "60.20B", + "label": "Edition de chaînes thématiques" + }, + { + "id": "61", + "label": "Télécommunications" + }, + { + "id": "61.1", + "label": "Télécommunications filaires" + }, + { + "id": "61.10", + "label": "Télécommunications filaires" + }, + { + "id": "61.10Z", + "label": "Télécommunications filaires" + }, + { + "id": "61.2", + "label": "Télécommunications sans fil" + }, + { + "id": "61.20", + "label": "Télécommunications sans fil" + }, + { + "id": "61.20Z", + "label": "Télécommunications sans fil" + }, + { + "id": "61.3", + "label": "Télécommunications par satellite" + }, + { + "id": "61.30", + "label": "Télécommunications par satellite" + }, + { + "id": "61.30Z", + "label": "Télécommunications par satellite" + }, + { + "id": "61.9", + "label": "Autres activités de télécommunication" + }, + { + "id": "61.90", + "label": "Autres activités de télécommunication" + }, + { + "id": "61.90Z", + "label": "Autres activités de télécommunication" + }, + { + "id": "62", + "label": "Programmation, conseil et autres activités informatiques" + }, + { + "id": "62.0", + "label": "Programmation, conseil et autres activités informatiques" + }, + { + "id": "62.01", + "label": "Programmation informatique" + }, + { + "id": "62.01Z", + "label": "Programmation informatique" + }, + { + "id": "62.02", + "label": "Conseil informatique" + }, + { + "id": "62.02A", + "label": "Conseil en systèmes et logiciels informatiques" + }, + { + "id": "62.02B", + "label": "Tierce maintenance de systèmes et d’applications informatiques" + }, + { + "id": "62.03", + "label": "Gestion d'installations informatiques" + }, + { + "id": "62.03Z", + "label": "Gestion d'installations informatiques" + }, + { + "id": "62.09", + "label": "Autres activités informatiques" + }, + { + "id": "62.09Z", + "label": "Autres activités informatiques" + }, + { + "id": "63", + "label": "Services d'information" + }, + { + "id": "63.1", + "label": "Traitement de données, hébergement et activités connexes ; portails Internet" + }, + { + "id": "63.11", + "label": "Traitement de données, hébergement et activités connexes" + }, + { + "id": "63.11Z", + "label": "Traitement de données, hébergement et activités connexes" + }, + { + "id": "63.12", + "label": "Portails Internet" + }, + { + "id": "63.12Z", + "label": "Portails Internet" + }, + { + "id": "63.9", + "label": "Autres services d'information" + }, + { + "id": "63.91", + "label": "Activités des agences de presse" + }, + { + "id": "63.91Z", + "label": "Activités des agences de presse" + }, + { + "id": "63.99", + "label": "Autres services d'information n.c.a." + }, + { + "id": "63.99Z", + "label": "Autres services d'information n.c.a." + }, + { + "id": "64", + "label": "Activités des services financiers, hors assurance et caisses de retraite" + }, + { + "id": "64.1", + "label": "Intermédiation monétaire" + }, + { + "id": "64.11", + "label": "Activités de banque centrale" + }, + { + "id": "64.11Z", + "label": "Activités de banque centrale" + }, + { + "id": "64.19", + "label": "Autres intermédiations monétaires" + }, + { + "id": "64.19Z", + "label": "Autres intermédiations monétaires" + }, + { + "id": "64.2", + "label": "Activités des sociétés holding" + }, + { + "id": "64.20", + "label": "Activités des sociétés holding" + }, + { + "id": "64.20Z", + "label": "Activités des sociétés holding" + }, + { + "id": "64.3", + "label": "Fonds de placement et entités financières similaires" + }, + { + "id": "64.30", + "label": "Fonds de placement et entités financières similaires" + }, + { + "id": "64.30Z", + "label": "Fonds de placement et entités financières similaires" + }, + { + "id": "64.9", + "label": "Autres activités des services financiers, hors assurance et caisses de retraite" + }, + { + "id": "64.91", + "label": "Crédit-bail" + }, + { + "id": "64.91Z", + "label": "Crédit-bail" + }, + { + "id": "64.92", + "label": "Autre distribution de crédit" + }, + { + "id": "64.92Z", + "label": "Autre distribution de crédit" + }, + { + "id": "64.99", + "label": "Autres activités des services financiers, hors assurance et caisses de retraite, n.c.a." + }, + { + "id": "64.99Z", + "label": "Autres activités des services financiers, hors assurance et caisses de retraite, n.c.a." + }, + { + "id": "65", + "label": "Assurance" + }, + { + "id": "65.1", + "label": "Assurance" + }, + { + "id": "65.11", + "label": "Assurance vie" + }, + { + "id": "65.11Z", + "label": "Assurance vie" + }, + { + "id": "65.12", + "label": "Autres assurances" + }, + { + "id": "65.12Z", + "label": "Autres assurances" + }, + { + "id": "65.2", + "label": "Réassurance" + }, + { + "id": "65.20", + "label": "Réassurance" + }, + { + "id": "65.20Z", + "label": "Réassurance" + }, + { + "id": "65.3", + "label": "Caisses de retraite" + }, + { + "id": "65.30", + "label": "Caisses de retraite" + }, + { + "id": "65.30Z", + "label": "Caisses de retraite" + }, + { + "id": "66", + "label": "Activités auxiliaires de services financiers et d'assurance" + }, + { + "id": "66.1", + "label": "Activités auxiliaires de services financiers, hors assurance et caisses de retraite" + }, + { + "id": "66.11", + "label": "Administration de marchés financiers" + }, + { + "id": "66.11Z", + "label": "Administration de marchés financiers" + }, + { + "id": "66.12", + "label": "Courtage de valeurs mobilières et de marchandises" + }, + { + "id": "66.12Z", + "label": "Courtage de valeurs mobilières et de marchandises" + }, + { + "id": "66.19", + "label": "Autres activités auxiliaires de services financiers, hors assurance et caisses de retraite" + }, + { + "id": "66.19A", + "label": "Supports juridiques de gestion de patrimoine mobilier" + }, + { + "id": "66.19B", + "label": "Autres activités auxiliaires de services financiers, hors assurance et caisses de retraite, n.c.a." + }, + { + "id": "66.2", + "label": "Activités auxiliaires d'assurance et de caisses de retraite" + }, + { + "id": "66.21", + "label": "Évaluation des risques et dommages" + }, + { + "id": "66.21Z", + "label": "Évaluation des risques et dommages" + }, + { + "id": "66.22", + "label": "Activités des agents et courtiers d'assurances" + }, + { + "id": "66.22Z", + "label": "Activités des agents et courtiers d'assurances" + }, + { + "id": "66.29", + "label": "Autres activités auxiliaires d'assurance et de caisses de retraite" + }, + { + "id": "66.29Z", + "label": "Autres activités auxiliaires d'assurance et de caisses de retraite" + }, + { + "id": "66.3", + "label": "Gestion de fonds" + }, + { + "id": "66.30", + "label": "Gestion de fonds" + }, + { + "id": "66.30Z", + "label": "Gestion de fonds" + }, + { + "id": "68", + "label": "Activités immobilières" + }, + { + "id": "68.1", + "label": "Activités des marchands de biens immobiliers" + }, + { + "id": "68.10", + "label": "Activités des marchands de biens immobiliers" + }, + { + "id": "68.10Z", + "label": "Activités des marchands de biens immobiliers" + }, + { + "id": "68.2", + "label": "Location et exploitation de biens immobiliers propres ou loués" + }, + { + "id": "68.20", + "label": "Location et exploitation de biens immobiliers propres ou loués" + }, + { + "id": "68.20A", + "label": "Location de logements" + }, + { + "id": "68.20B", + "label": "Location de terrains et d'autres biens immobiliers" + }, + { + "id": "68.3", + "label": "Activités immobilières pour compte de tiers" + }, + { + "id": "68.31", + "label": "Agences immobilières" + }, + { + "id": "68.31Z", + "label": "Agences immobilières" + }, + { + "id": "68.32", + "label": "Administration de biens immobiliers" + }, + { + "id": "68.32A", + "label": "Administration d'immeubles et autres biens immobiliers" + }, + { + "id": "68.32B", + "label": "Supports juridiques de gestion de patrimoine immobilier" + }, + { + "id": "69", + "label": "Activités juridiques et comptables" + }, + { + "id": "69.1", + "label": "Activités juridiques" + }, + { + "id": "69.10", + "label": "Activités juridiques" + }, + { + "id": "69.10Z", + "label": "Activités juridiques" + }, + { + "id": "69.2", + "label": "Activités comptables" + }, + { + "id": "69.20", + "label": "Activités comptables" + }, + { + "id": "69.20Z", + "label": "Activités comptables" + }, + { + "id": "70", + "label": "Activités des sièges sociaux ; conseil de gestion" + }, + { + "id": "70.1", + "label": "Activités des sièges sociaux" + }, + { + "id": "70.10", + "label": "Activités des sièges sociaux" + }, + { + "id": "70.10Z", + "label": "Activités des sièges sociaux" + }, + { + "id": "70.2", + "label": "Conseil de gestion" + }, + { + "id": "70.21", + "label": "Conseil en relations publiques et communication" + }, + { + "id": "70.21Z", + "label": "Conseil en relations publiques et communication" + }, + { + "id": "70.22", + "label": "Conseil pour les affaires et autres conseils de gestion" + }, + { + "id": "70.22Z", + "label": "Conseil pour les affaires et autres conseils de gestion" + }, + { + "id": "71", + "label": "Activités d'architecture et d'ingénierie ; activités de contrôle et analyses techniques" + }, + { + "id": "71.1", + "label": "Activités d'architecture et d'ingénierie" + }, + { + "id": "71.11", + "label": "Activités d'architecture" + }, + { + "id": "71.11Z", + "label": "Activités d'architecture" + }, + { + "id": "71.12", + "label": "Activités d'ingénierie" + }, + { + "id": "71.12A", + "label": "Activité des géomètres" + }, + { + "id": "71.12B", + "label": "Ingénierie, études techniques" + }, + { + "id": "71.2", + "label": "Activités de contrôle et analyses techniques" + }, + { + "id": "71.20", + "label": "Activités de contrôle et analyses techniques" + }, + { + "id": "71.20A", + "label": "Contrôle technique automobile" + }, + { + "id": "71.20B", + "label": "Analyses, essais et inspections techniques" + }, + { + "id": "72", + "label": "Recherche-développement scientifique" + }, + { + "id": "72.1", + "label": "Recherche-développement en sciences physiques et naturelles" + }, + { + "id": "72.11", + "label": "Recherche-développement en biotechnologie" + }, + { + "id": "72.11Z", + "label": "Recherche-développement en biotechnologie" + }, + { + "id": "72.19", + "label": "Recherche-développement en autres sciences physiques et naturelles" + }, + { + "id": "72.19Z", + "label": "Recherche-développement en autres sciences physiques et naturelles" + }, + { + "id": "72.2", + "label": "Recherche-développement en sciences humaines et sociales" + }, + { + "id": "72.20", + "label": "Recherche-développement en sciences humaines et sociales" + }, + { + "id": "72.20Z", + "label": "Recherche-développement en sciences humaines et sociales" + }, + { + "id": "73", + "label": "Publicité et études de marché" + }, + { + "id": "73.1", + "label": "Publicité" + }, + { + "id": "73.11", + "label": "Activités des agences de publicité" + }, + { + "id": "73.11Z", + "label": "Activités des agences de publicité" + }, + { + "id": "73.12", + "label": "Régie publicitaire de médias" + }, + { + "id": "73.12Z", + "label": "Régie publicitaire de médias" + }, + { + "id": "73.2", + "label": "Études de marché et sondages" + }, + { + "id": "73.20", + "label": "Études de marché et sondages" + }, + { + "id": "73.20Z", + "label": "Études de marché et sondages" + }, + { + "id": "74", + "label": "Autres activités spécialisées, scientifiques et techniques" + }, + { + "id": "74.1", + "label": "Activités spécialisées de design" + }, + { + "id": "74.10", + "label": "Activités spécialisées de design" + }, + { + "id": "74.10Z", + "label": "Activités spécialisées de design" + }, + { + "id": "74.2", + "label": "Activités photographiques" + }, + { + "id": "74.20", + "label": "Activités photographiques" + }, + { + "id": "74.20Z", + "label": "Activités photographiques" + }, + { + "id": "74.3", + "label": "Traduction et interprétation" + }, + { + "id": "74.30", + "label": "Traduction et interprétation" + }, + { + "id": "74.30Z", + "label": "Traduction et interprétation" + }, + { + "id": "74.9", + "label": "Autres activités spécialisées, scientifiques et techniques n.c.a." + }, + { + "id": "74.90", + "label": "Autres activités spécialisées, scientifiques et techniques n.c.a." + }, + { + "id": "74.90A", + "label": "Activité des économistes de la construction" + }, + { + "id": "74.90B", + "label": "Activités spécialisées, scientifiques et techniques diverses" + }, + { + "id": "75", + "label": "Activités vétérinaires" + }, + { + "id": "75.0", + "label": "Activités vétérinaires" + }, + { + "id": "75.00", + "label": "Activités vétérinaires" + }, + { + "id": "75.00Z", + "label": "Activités vétérinaires" + }, + { + "id": "77", + "label": "Activités de location et location-bail" + }, + { + "id": "77.1", + "label": "Location et location-bail de véhicules automobiles" + }, + { + "id": "77.11", + "label": "Location et location-bail de voitures et de véhicules automobiles légers" + }, + { + "id": "77.11A", + "label": "Location de courte durée de voitures et de véhicules automobiles légers" + }, + { + "id": "77.11B", + "label": "Location de longue durée de voitures et de véhicules automobiles légers" + }, + { + "id": "77.12", + "label": "Location et location-bail de camions" + }, + { + "id": "77.12Z", + "label": "Location et location-bail de camions" + }, + { + "id": "77.2", + "label": "Location et location-bail de biens personnels et domestiques" + }, + { + "id": "77.21", + "label": "Location et location-bail d'articles de loisirs et de sport" + }, + { + "id": "77.21Z", + "label": "Location et location-bail d'articles de loisirs et de sport" + }, + { + "id": "77.22", + "label": "Location de vidéocassettes et disques vidéo" + }, + { + "id": "77.22Z", + "label": "Location de vidéocassettes et disques vidéo" + }, + { + "id": "77.29", + "label": "Location et location-bail d'autres biens personnels et domestiques" + }, + { + "id": "77.29Z", + "label": "Location et location-bail d'autres biens personnels et domestiques" + }, + { + "id": "77.3", + "label": "Location et location-bail d'autres machines, équipements et biens" + }, + { + "id": "77.31", + "label": "Location et location-bail de machines et équipements agricoles" + }, + { + "id": "77.31Z", + "label": "Location et location-bail de machines et équipements agricoles" + }, + { + "id": "77.32", + "label": "Location et location-bail de machines et équipements pour la construction" + }, + { + "id": "77.32Z", + "label": "Location et location-bail de machines et équipements pour la construction" + }, + { + "id": "77.33", + "label": "Location et location-bail de machines de bureau et de matériel informatique" + }, + { + "id": "77.33Z", + "label": "Location et location-bail de machines de bureau et de matériel informatique" + }, + { + "id": "77.34", + "label": "Location et location-bail de matériels de transport par eau" + }, + { + "id": "77.34Z", + "label": "Location et location-bail de matériels de transport par eau" + }, + { + "id": "77.35", + "label": "Location et location-bail de matériels de transport aérien" + }, + { + "id": "77.35Z", + "label": "Location et location-bail de matériels de transport aérien" + }, + { + "id": "77.39", + "label": "Location et location-bail d'autres machines, équipements et biens matériels n.c.a." + }, + { + "id": "77.39Z", + "label": "Location et location-bail d'autres machines, équipements et biens matériels n.c.a." + }, + { + "id": "77.4", + "label": "Location-bail de propriété intellectuelle et de produits similaires, à l'exception des œuvres soumises à copyright" + }, + { + "id": "77.40", + "label": "Location-bail de propriété intellectuelle et de produits similaires, à l'exception des œuvres soumises à copyright" + }, + { + "id": "77.40Z", + "label": "Location-bail de propriété intellectuelle et de produits similaires, à l'exception des œuvres soumises à copyright" + }, + { + "id": "78", + "label": "Activités liées à l'emploi" + }, + { + "id": "78.1", + "label": "Activités des agences de placement de main-d'œuvre" + }, + { + "id": "78.10", + "label": "Activités des agences de placement de main-d'œuvre" + }, + { + "id": "78.10Z", + "label": "Activités des agences de placement de main-d'œuvre" + }, + { + "id": "78.2", + "label": "Activités des agences de travail temporaire" + }, + { + "id": "78.20", + "label": "Activités des agences de travail temporaire" + }, + { + "id": "78.20Z", + "label": "Activités des agences de travail temporaire" + }, + { + "id": "78.3", + "label": "Autre mise à disposition de ressources humaines" + }, + { + "id": "78.30", + "label": "Autre mise à disposition de ressources humaines" + }, + { + "id": "78.30Z", + "label": "Autre mise à disposition de ressources humaines" + }, + { + "id": "79", + "label": "Activités des agences de voyage, voyagistes, services de réservation et activités connexes" + }, + { + "id": "79.1", + "label": "Activités des agences de voyage et voyagistes" + }, + { + "id": "79.11", + "label": "Activités des agences de voyage" + }, + { + "id": "79.11Z", + "label": "Activités des agences de voyage" + }, + { + "id": "79.12", + "label": "Activités des voyagistes" + }, + { + "id": "79.12Z", + "label": "Activités des voyagistes" + }, + { + "id": "79.9", + "label": "Autres services de réservation et activités connexes" + }, + { + "id": "79.90", + "label": "Autres services de réservation et activités connexes" + }, + { + "id": "79.90Z", + "label": "Autres services de réservation et activités connexes" + }, + { + "id": "80", + "label": "Enquêtes et sécurité" + }, + { + "id": "80.1", + "label": "Activités de sécurité privée" + }, + { + "id": "80.10", + "label": "Activités de sécurité privée" + }, + { + "id": "80.10Z", + "label": "Activités de sécurité privée" + }, + { + "id": "80.2", + "label": "Activités liées aux systèmes de sécurité" + }, + { + "id": "80.20", + "label": "Activités liées aux systèmes de sécurité" + }, + { + "id": "80.20Z", + "label": "Activités liées aux systèmes de sécurité" + }, + { + "id": "80.3", + "label": "Activités d'enquête" + }, + { + "id": "80.30", + "label": "Activités d'enquête" + }, + { + "id": "80.30Z", + "label": "Activités d'enquête" + }, + { + "id": "81", + "label": "Services relatifs aux bâtiments et aménagement paysager" + }, + { + "id": "81.1", + "label": "Activités combinées de soutien lié aux bâtiments" + }, + { + "id": "81.10", + "label": "Activités combinées de soutien lié aux bâtiments" + }, + { + "id": "81.10Z", + "label": "Activités combinées de soutien lié aux bâtiments" + }, + { + "id": "81.2", + "label": "Activités de nettoyage" + }, + { + "id": "81.21", + "label": "Nettoyage courant des bâtiments" + }, + { + "id": "81.21Z", + "label": "Nettoyage courant des bâtiments" + }, + { + "id": "81.22", + "label": "Autres activités de nettoyage des bâtiments et nettoyage industriel" + }, + { + "id": "81.22Z", + "label": "Autres activités de nettoyage des bâtiments et nettoyage industriel" + }, + { + "id": "81.29", + "label": "Autres activités de nettoyage" + }, + { + "id": "81.29A", + "label": "Désinfection, désinsectisation, dératisation" + }, + { + "id": "81.29B", + "label": "Autres activités de nettoyage n.c.a." + }, + { + "id": "81.3", + "label": "Services d'aménagement paysager" + }, + { + "id": "81.30", + "label": "Services d'aménagement paysager" + }, + { + "id": "81.30Z", + "label": "Services d'aménagement paysager" + }, + { + "id": "82", + "label": "Activités administratives et autres activités de soutien aux entreprises" + }, + { + "id": "82.1", + "label": "Activités administratives" + }, + { + "id": "82.11", + "label": "Services administratifs combinés de bureau" + }, + { + "id": "82.11Z", + "label": "Services administratifs combinés de bureau" + }, + { + "id": "82.19", + "label": "Photocopie, préparation de documents et autres activités spécialisées de soutien de bureau" + }, + { + "id": "82.19Z", + "label": "Photocopie, préparation de documents et autres activités spécialisées de soutien de bureau" + }, + { + "id": "82.2", + "label": "Activités de centres d'appels" + }, + { + "id": "82.20", + "label": "Activités de centres d'appels" + }, + { + "id": "82.20Z", + "label": "Activités de centres d'appels" + }, + { + "id": "82.3", + "label": "Organisation de salons professionnels et congrès" + }, + { + "id": "82.30", + "label": "Organisation de salons professionnels et congrès" + }, + { + "id": "82.30Z", + "label": "Organisation de foires, salons professionnels et congrès" + }, + { + "id": "82.9", + "label": "Activités de soutien aux entreprises n.c.a." + }, + { + "id": "82.91", + "label": "Activités des agences de recouvrement de factures et des sociétés d'information financière sur la clientèle" + }, + { + "id": "82.91Z", + "label": "Activités des agences de recouvrement de factures et des sociétés d'information financière sur la clientèle" + }, + { + "id": "82.92", + "label": "Activités de conditionnement" + }, + { + "id": "82.92Z", + "label": "Activités de conditionnement" + }, + { + "id": "82.99", + "label": "Autres activités de soutien aux entreprises n.c.a." + }, + { + "id": "82.99Z", + "label": "Autres activités de soutien aux entreprises n.c.a." + }, + { + "id": "84", + "label": "Administration publique et défense ; sécurité sociale obligatoire" + }, + { + "id": "84.1", + "label": "Administration générale, économique et sociale" + }, + { + "id": "84.11", + "label": "Administration publique générale" + }, + { + "id": "84.11Z", + "label": "Administration publique générale" + }, + { + "id": "84.12", + "label": "Administration publique (tutelle) de la santé, de la formation, de la culture et des services sociaux, autre que sécurité sociale" + }, + { + "id": "84.12Z", + "label": "Administration publique (tutelle) de la santé, de la formation, de la culture et des services sociaux, autre que sécurité sociale" + }, + { + "id": "84.13", + "label": "Administration publique (tutelle) des activités économiques" + }, + { + "id": "84.13Z", + "label": "Administration publique (tutelle) des activités économiques" + }, + { + "id": "84.2", + "label": "Services de prérogative publique" + }, + { + "id": "84.21", + "label": "Affaires étrangères" + }, + { + "id": "84.21Z", + "label": "Affaires étrangères" + }, + { + "id": "84.22", + "label": "Défense" + }, + { + "id": "84.22Z", + "label": "Défense" + }, + { + "id": "84.23", + "label": "Justice" + }, + { + "id": "84.23Z", + "label": "Justice" + }, + { + "id": "84.24", + "label": "Activités d’ordre public et de sécurité" + }, + { + "id": "84.24Z", + "label": "Activités d’ordre public et de sécurité" + }, + { + "id": "84.25", + "label": "Services du feu et de secours" + }, + { + "id": "84.25Z", + "label": "Services du feu et de secours" + }, + { + "id": "84.3", + "label": "Sécurité sociale obligatoire" + }, + { + "id": "84.30", + "label": "Sécurité sociale obligatoire" + }, + { + "id": "84.30A", + "label": "Activités générales de sécurité sociale" + }, + { + "id": "84.30B", + "label": "Gestion des retraites complémentaires" + }, + { + "id": "84.30C", + "label": "Distribution sociale de revenus" + }, + { + "id": "85", + "label": "Enseignement" + }, + { + "id": "85.1", + "label": "Enseignement pré-primaire" + }, + { + "id": "85.10", + "label": "Enseignement pré-primaire" + }, + { + "id": "85.10Z", + "label": "Enseignement pré-primaire" + }, + { + "id": "85.2", + "label": "Enseignement primaire" + }, + { + "id": "85.20", + "label": "Enseignement primaire" + }, + { + "id": "85.20Z", + "label": "Enseignement primaire" + }, + { + "id": "85.3", + "label": "Enseignement secondaire" + }, + { + "id": "85.31", + "label": "Enseignement secondaire général" + }, + { + "id": "85.31Z", + "label": "Enseignement secondaire général" + }, + { + "id": "85.32", + "label": "Enseignement secondaire technique ou professionnel" + }, + { + "id": "85.32Z", + "label": "Enseignement secondaire technique ou professionnel" + }, + { + "id": "85.4", + "label": "Enseignement supérieur et post-secondaire non supérieur" + }, + { + "id": "85.41", + "label": "Enseignement post-secondaire non supérieur" + }, + { + "id": "85.41Z", + "label": "Enseignement post-secondaire non supérieur" + }, + { + "id": "85.42", + "label": "Enseignement supérieur" + }, + { + "id": "85.42Z", + "label": "Enseignement supérieur" + }, + { + "id": "85.5", + "label": "Autres activités d'enseignement" + }, + { + "id": "85.51", + "label": "Enseignement de disciplines sportives et d'activités de loisirs" + }, + { + "id": "85.51Z", + "label": "Enseignement de disciplines sportives et d'activités de loisirs" + }, + { + "id": "85.52", + "label": "Enseignement culturel" + }, + { + "id": "85.52Z", + "label": "Enseignement culturel" + }, + { + "id": "85.53", + "label": "Enseignement de la conduite" + }, + { + "id": "85.53Z", + "label": "Enseignement de la conduite" + }, + { + "id": "85.59", + "label": "Enseignements divers" + }, + { + "id": "85.59A", + "label": "Formation continue d'adultes" + }, + { + "id": "85.59B", + "label": "Autres enseignements" + }, + { + "id": "85.6", + "label": "Activités de soutien à l'enseignement" + }, + { + "id": "85.60", + "label": "Activités de soutien à l'enseignement" + }, + { + "id": "85.60Z", + "label": "Activités de soutien à l'enseignement" + }, + { + "id": "86", + "label": "Activités pour la santé humaine" + }, + { + "id": "86.1", + "label": "Activités hospitalières" + }, + { + "id": "86.10", + "label": "Activités hospitalières" + }, + { + "id": "86.10Z", + "label": "Activités hospitalières" + }, + { + "id": "86.2", + "label": "Activité des médecins et des dentistes" + }, + { + "id": "86.21", + "label": "Activité des médecins généralistes" + }, + { + "id": "86.21Z", + "label": "Activité des médecins généralistes" + }, + { + "id": "86.22", + "label": "Activité des médecins spécialistes" + }, + { + "id": "86.22A", + "label": "Activités de radiodiagnostic et de radiothérapie" + }, + { + "id": "86.22B", + "label": "Activités chirurgicales" + }, + { + "id": "86.22C", + "label": "Autres activités des médecins spécialistes" + }, + { + "id": "86.23", + "label": "Pratique dentaire" + }, + { + "id": "86.23Z", + "label": "Pratique dentaire" + }, + { + "id": "86.9", + "label": "Autres activités pour la santé humaine" + }, + { + "id": "86.90", + "label": "Autres activités pour la santé humaine" + }, + { + "id": "86.90A", + "label": "Ambulances" + }, + { + "id": "86.90B", + "label": "Laboratoires d'analyses médicales" + }, + { + "id": "86.90C", + "label": "Centres de collecte et banques d'organes" + }, + { + "id": "86.90D", + "label": "Activités des infirmiers et des sages-femmes" + }, + { + "id": "86.90E", + "label": "Activités des professionnels de la rééducation, de l’appareillage et des pédicures-podologues" + }, + { + "id": "86.90F", + "label": "Activités de santé humaine non classées ailleurs" + }, + { + "id": "87", + "label": "Hébergement médico-social et social" + }, + { + "id": "87.1", + "label": "Hébergement médicalisé" + }, + { + "id": "87.10", + "label": "Hébergement médicalisé" + }, + { + "id": "87.10A", + "label": "Hébergement médicalisé pour personnes âgées" + }, + { + "id": "87.10B", + "label": "Hébergement médicalisé pour enfants handicapés" + }, + { + "id": "87.10C", + "label": "Hébergement médicalisé pour adultes handicapés et autre hébergement médicalisé" + }, + { + "id": "87.2", + "label": "Hébergement social pour personnes handicapées mentales, malades mentales et toxicomanes" + }, + { + "id": "87.20", + "label": "Hébergement social pour personnes handicapées mentales, malades mentales et toxicomanes" + }, + { + "id": "87.20A", + "label": "Hébergement social pour handicapés mentaux et malades mentaux" + }, + { + "id": "87.20B", + "label": "Hébergement social pour toxicomanes" + }, + { + "id": "87.3", + "label": "Hébergement social pour personnes âgées ou handicapées physiques" + }, + { + "id": "87.30", + "label": "Hébergement social pour personnes âgées ou handicapées physiques" + }, + { + "id": "87.30A", + "label": "Hébergement social pour personnes âgées" + }, + { + "id": "87.30B", + "label": "Hébergement social pour handicapés physiques" + }, + { + "id": "87.9", + "label": "Autres activités d’hébergement social" + }, + { + "id": "87.90", + "label": "Autres activités d’hébergement social" + }, + { + "id": "87.90A", + "label": "Hébergement social pour enfants en difficultés" + }, + { + "id": "87.90B", + "label": "Hébergement social pour adultes et familles en difficultés et autre hébergement social" + }, + { + "id": "88", + "label": "Action sociale sans hébergement" + }, + { + "id": "88.1", + "label": "Action sociale sans hébergement pour personnes âgées et pour personnes handicapées" + }, + { + "id": "88.10", + "label": "Action sociale sans hébergement pour personnes âgées et pour personnes handicapées" + }, + { + "id": "88.10A", + "label": "Aide à domicile" + }, + { + "id": "88.10B", + "label": "Accueil ou accompagnement sans hébergement d’adultes handicapés ou de personnes âgées" + }, + { + "id": "88.10C", + "label": "Aide par le travail" + }, + { + "id": "88.9", + "label": "Autre action sociale sans hébergement" + }, + { + "id": "88.91", + "label": "Action sociale sans hébergement pour jeunes enfants" + }, + { + "id": "88.91A", + "label": "Accueil de jeunes enfants" + }, + { + "id": "88.91B", + "label": "Accueil ou accompagnement sans hébergement d’enfants handicapés" + }, + { + "id": "88.99", + "label": "Autre action sociale sans hébergement n.c.a." + }, + { + "id": "88.99A", + "label": "Autre accueil ou accompagnement sans hébergement d’enfants et d’adolescents" + }, + { + "id": "88.99B", + "label": "Action sociale sans hébergement n.c.a." + }, + { + "id": "90", + "label": "Activités créatives, artistiques et de spectacle" + }, + { + "id": "90.0", + "label": "Activités créatives, artistiques et de spectacle" + }, + { + "id": "90.01", + "label": "Arts du spectacle vivant" + }, + { + "id": "90.01Z", + "label": "Arts du spectacle vivant" + }, + { + "id": "90.02", + "label": "Activités de soutien au spectacle vivant" + }, + { + "id": "90.02Z", + "label": "Activités de soutien au spectacle vivant" + }, + { + "id": "90.03", + "label": "Création artistique" + }, + { + "id": "90.03A", + "label": "Création artistique relevant des arts plastiques" + }, + { + "id": "90.03B", + "label": "Autre création artistique" + }, + { + "id": "90.04", + "label": "Gestion de salles de spectacles" + }, + { + "id": "90.04Z", + "label": "Gestion de salles de spectacles" + }, + { + "id": "91", + "label": "Bibliothèques, archives, musées et autres activités culturelles" + }, + { + "id": "91.0", + "label": "Bibliothèques, archives, musées et autres activités culturelles" + }, + { + "id": "91.01", + "label": "Gestion des bibliothèques et des archives" + }, + { + "id": "91.01Z", + "label": "Gestion des bibliothèques et des archives" + }, + { + "id": "91.02", + "label": "Gestion des musées" + }, + { + "id": "91.02Z", + "label": "Gestion des musées" + }, + { + "id": "91.03", + "label": "Gestion des sites et monuments historiques et des attractions touristiques similaires" + }, + { + "id": "91.03Z", + "label": "Gestion des sites et monuments historiques et des attractions touristiques similaires" + }, + { + "id": "91.04", + "label": "Gestion des jardins botaniques et zoologiques et des réserves naturelles" + }, + { + "id": "91.04Z", + "label": "Gestion des jardins botaniques et zoologiques et des réserves naturelles" + }, + { + "id": "92", + "label": "Organisation de jeux de hasard et d'argent" + }, + { + "id": "92.0", + "label": "Organisation de jeux de hasard et d'argent" + }, + { + "id": "92.00", + "label": "Organisation de jeux de hasard et d'argent" + }, + { + "id": "92.00Z", + "label": "Organisation de jeux de hasard et d'argent" + }, + { + "id": "93", + "label": "Activités sportives, récréatives et de loisirs" + }, + { + "id": "93.1", + "label": "Activités liées au sport" + }, + { + "id": "93.11", + "label": "Gestion d'installations sportives" + }, + { + "id": "93.11Z", + "label": "Gestion d'installations sportives" + }, + { + "id": "93.12", + "label": "Activités de clubs de sports" + }, + { + "id": "93.12Z", + "label": "Activités de clubs de sports" + }, + { + "id": "93.13", + "label": "Activités des centres de culture physique" + }, + { + "id": "93.13Z", + "label": "Activités des centres de culture physique" + }, + { + "id": "93.19", + "label": "Autres activités liées au sport" + }, + { + "id": "93.19Z", + "label": "Autres activités liées au sport" + }, + { + "id": "93.2", + "label": "Activités récréatives et de loisirs" + }, + { + "id": "93.21", + "label": "Activités des parcs d'attractions et parcs à thèmes" + }, + { + "id": "93.21Z", + "label": "Activités des parcs d'attractions et parcs à thèmes" + }, + { + "id": "93.29", + "label": "Autres activités récréatives et de loisirs" + }, + { + "id": "93.29Z", + "label": "Autres activités récréatives et de loisirs" + }, + { + "id": "94", + "label": "Activités des organisations associatives" + }, + { + "id": "94.1", + "label": "Activités des organisations économiques, patronales et professionnelles" + }, + { + "id": "94.11", + "label": "Activités des organisations patronales et consulaires" + }, + { + "id": "94.11Z", + "label": "Activités des organisations patronales et consulaires" + }, + { + "id": "94.12", + "label": "Activités des organisations professionnelles" + }, + { + "id": "94.12Z", + "label": "Activités des organisations professionnelles" + }, + { + "id": "94.2", + "label": "Activités des syndicats de salariés" + }, + { + "id": "94.20", + "label": "Activités des syndicats de salariés" + }, + { + "id": "94.20Z", + "label": "Activités des syndicats de salariés" + }, + { + "id": "94.9", + "label": "Activités des autres organisations associatives" + }, + { + "id": "94.91", + "label": "Activités des organisations religieuses" + }, + { + "id": "94.91Z", + "label": "Activités des organisations religieuses" + }, + { + "id": "94.92", + "label": "Activités des organisations politiques" + }, + { + "id": "94.92Z", + "label": "Activités des organisations politiques" + }, + { + "id": "94.99", + "label": "Activités des organisations associatives n.c.a." + }, + { + "id": "94.99Z", + "label": "Autres organisations fonctionnant par adhésion volontaire" + }, + { + "id": "95", + "label": "Réparation d'ordinateurs et de biens personnels et domestiques" + }, + { + "id": "95.1", + "label": "Réparation d'ordinateurs et d'équipements de communication" + }, + { + "id": "95.11", + "label": "Réparation d'ordinateurs et d'équipements périphériques" + }, + { + "id": "95.11Z", + "label": "Réparation d'ordinateurs et d'équipements périphériques" + }, + { + "id": "95.12", + "label": "Réparation d'équipements de communication" + }, + { + "id": "95.12Z", + "label": "Réparation d'équipements de communication" + }, + { + "id": "95.2", + "label": "Réparation de biens personnels et domestiques" + }, + { + "id": "95.21", + "label": "Réparation de produits électroniques grand public" + }, + { + "id": "95.21Z", + "label": "Réparation de produits électroniques grand public" + }, + { + "id": "95.22", + "label": "Réparation d'appareils électroménagers et d'équipements pour la maison et le jardin" + }, + { + "id": "95.22Z", + "label": "Réparation d'appareils électroménagers et d'équipements pour la maison et le jardin" + }, + { + "id": "95.23", + "label": "Réparation de chaussures et d'articles en cuir" + }, + { + "id": "95.23Z", + "label": "Réparation de chaussures et d'articles en cuir" + }, + { + "id": "95.24", + "label": "Réparation de meubles et d'équipements du foyer" + }, + { + "id": "95.24Z", + "label": "Réparation de meubles et d'équipements du foyer" + }, + { + "id": "95.25", + "label": "Réparation d'articles d'horlogerie et de bijouterie" + }, + { + "id": "95.25Z", + "label": "Réparation d'articles d'horlogerie et de bijouterie" + }, + { + "id": "95.29", + "label": "Réparation d'autres biens personnels et domestiques" + }, + { + "id": "95.29Z", + "label": "Réparation d'autres biens personnels et domestiques" + }, + { + "id": "96", + "label": "Autres services personnels" + }, + { + "id": "96.0", + "label": "Autres services personnels" + }, + { + "id": "96.01", + "label": "Blanchisserie-teinturerie" + }, + { + "id": "96.01A", + "label": "Blanchisserie-teinturerie de gros" + }, + { + "id": "96.01B", + "label": "Blanchisserie-teinturerie de détail" + }, + { + "id": "96.02", + "label": "Coiffure et soins de beauté" + }, + { + "id": "96.02A", + "label": "Coiffure" + }, + { + "id": "96.02B", + "label": "Soins de beauté" + }, + { + "id": "96.03", + "label": "Services funéraires" + }, + { + "id": "96.03Z", + "label": "Services funéraires" + }, + { + "id": "96.04", + "label": "Entretien corporel" + }, + { + "id": "96.04Z", + "label": "Entretien corporel" + }, + { + "id": "96.09", + "label": "Autres services personnels n.c.a." + }, + { + "id": "96.09Z", + "label": "Autres services personnels n.c.a." + }, + { + "id": "97", + "label": "Activités des ménages en tant qu'employeurs de personnel domestique" + }, + { + "id": "97.0", + "label": "Activités des ménages en tant qu'employeurs de personnel domestique" + }, + { + "id": "97.00", + "label": "Activités des ménages en tant qu'employeurs de personnel domestique" + }, + { + "id": "97.00Z", + "label": "Activités des ménages en tant qu'employeurs de personnel domestique" + }, + { + "id": "98", + "label": "Activités indifférenciées des ménages en tant que producteurs de biens et services pour usage propre" + }, + { + "id": "98.1", + "label": "Activités indifférenciées des ménages en tant que producteurs de biens pour usage propre" + }, + { + "id": "98.10", + "label": "Activités indifférenciées des ménages en tant que producteurs de biens pour usage propre" + }, + { + "id": "98.10Z", + "label": "Activités indifférenciées des ménages en tant que producteurs de biens pour usage propre" + }, + { + "id": "98.2", + "label": "Activités indifférenciées des ménages en tant que producteurs de services pour usage propre" + }, + { + "id": "98.20", + "label": "Activités indifférenciées des ménages en tant que producteurs de services pour usage propre" + }, + { + "id": "98.20Z", + "label": "Activités indifférenciées des ménages en tant que producteurs de services pour usage propre" + }, + { + "id": "99", + "label": "Activités des organisations et organismes extraterritoriaux" + }, + { + "id": "99.0", + "label": "Activités des organisations et organismes extraterritoriaux" + }, + { + "id": "99.00", + "label": "Activités des organisations et organismes extraterritoriaux" + }, + { + "id": "99.00Z", + "label": "Activités des organisations et organismes extraterritoriaux" + } +] diff --git a/package.json b/package.json new file mode 100644 index 0000000..e070733 --- /dev/null +++ b/package.json @@ -0,0 +1,42 @@ +{ + "name": "e-cosplay", + "private": true, + "version": "0.0.0", + "type": "module", + "main": "index.js", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview", + "test": "vitest run", + "test:coverage": "vitest run --coverage" + }, + "devDependencies": { + "@happy-dom/global-registrator": "^20.8.4", + "@hotwired/stimulus": "^3.0.0", + "@spomky-labs/pwa-bundle": "file:vendor/spomky-labs/pwa-bundle/assets", + "@tailwindcss/postcss": "^4.1.18", + "@vitest/coverage-v8": "^4.1.0", + "eslint": "9", + "idb": "^8.0.3", + "idb-keyval": "^6.2.2", + "postcss": "^8.5.6", + "postcss-scss": "^4.0.9", + "rollup-plugin-javascript-obfuscator": "^1.0.4", + "sass": "^1.97.3", + "stylelint": "16", + "stylelint-config-standard-scss": "13", + "vite": "^7.3.1", + "vitest": "^4.1.0" + }, + "dependencies": { + "@grafikart/drop-files-element": "^1.0.9", + "@hotwired/turbo": "^8.0.23", + "@sentry/browser": "^10.38.0", + "@tailwindcss/vite": "^4.1.18", + "autoprefixer": "^10.4.24", + "tailwindcss": "^4.1.18", + "vite-plugin-compression": "^0.5.1", + "vite-plugin-favicon": "^1.0.8" + } +} diff --git a/phpstan.dist.neon b/phpstan.dist.neon new file mode 100644 index 0000000..e0de575 --- /dev/null +++ b/phpstan.dist.neon @@ -0,0 +1,8 @@ +parameters: + level: 6 + paths: + - bin/ + - config/ + - public/ + - src/ + - tests/ diff --git a/phpunit.dist.xml b/phpunit.dist.xml index 22bd879..e1e6993 100644 --- a/phpunit.dist.xml +++ b/phpunit.dist.xml @@ -20,6 +20,8 @@ tests + tests/Controller/StripeWebhookControllerTest.php + tests/Service/StripeServiceTest.php @@ -31,6 +33,14 @@ src + + src/Controller/StripeWebhookController.php + src/Service/StripeService.php + src/Service/PayoutPdfService.php + src/Service/BilletOrderService.php + src/Service/InvoiceService.php + src/Repository + Doctrine\Deprecations\Deprecation::trigger diff --git a/postcss.config.cjs b/postcss.config.cjs new file mode 100644 index 0000000..7ef716b --- /dev/null +++ b/postcss.config.cjs @@ -0,0 +1,7 @@ +module.exports = { + parser: 'postcss-scss', + plugins: { + '@tailwindcss/postcss': {}, + 'autoprefixer': {}, + }, +} diff --git a/public/Gemini_Generated_Image_p4g00sp4g00sp4g0.png:Zone.Identifier b/public/Gemini_Generated_Image_p4g00sp4g00sp4g0.png:Zone.Identifier new file mode 100644 index 0000000..d6c1ec6 Binary files /dev/null and b/public/Gemini_Generated_Image_p4g00sp4g00sp4g0.png:Zone.Identifier differ diff --git a/public/Gemini_Generated_Image_q1z0qzq1z0qzq1z0.png:Zone.Identifier b/public/Gemini_Generated_Image_q1z0qzq1z0qzq1z0.png:Zone.Identifier new file mode 100644 index 0000000..d6c1ec6 Binary files /dev/null and b/public/Gemini_Generated_Image_q1z0qzq1z0qzq1z0.png:Zone.Identifier differ diff --git a/public/JOAFE_PDF_Unitaire_20250013_00029.pdf b/public/JOAFE_PDF_Unitaire_20250013_00029.pdf new file mode 100644 index 0000000..b14743c Binary files /dev/null and b/public/JOAFE_PDF_Unitaire_20250013_00029.pdf differ diff --git a/public/avis-94312151700016-20260401111834.pdf b/public/avis-94312151700016-20260401111834.pdf new file mode 100644 index 0000000..c4fba40 Binary files /dev/null and b/public/avis-94312151700016-20260401111834.pdf differ diff --git a/public/bimi.svg b/public/bimi.svg new file mode 100644 index 0000000..f685675 --- /dev/null +++ b/public/bimi.svg @@ -0,0 +1,10 @@ + + + bimi-svg-tiny-12-ps + + + + + + + \ No newline at end of file diff --git a/public/favicon.png b/public/favicon.png new file mode 100644 index 0000000..f5ed9d7 Binary files /dev/null and b/public/favicon.png differ diff --git a/public/logo.jpg b/public/logo.jpg new file mode 100644 index 0000000..feb6bec Binary files /dev/null and b/public/logo.jpg differ diff --git a/public/marker.png b/public/marker.png new file mode 100644 index 0000000..0587826 Binary files /dev/null and b/public/marker.png differ diff --git a/sonar-project.properties b/sonar-project.properties new file mode 100644 index 0000000..204f4a1 --- /dev/null +++ b/sonar-project.properties @@ -0,0 +1,26 @@ +sonar.projectKey=crm-ecosplay +sonar.projectName=CRM Ecosplay + +sonar.sources=src +sonar.tests=tests + +sonar.language=php +sonar.sourceEncoding=UTF-8 + +sonar.php.version=8.4 + +# Exclusions +sonar.exclusions=vendor/**,var/**,public/bundles/**,node_modules/**,assets/vendor/**,migrations/** + +# Coverage +sonar.php.coverage.reportPaths=var/reports/coverage.xml +sonar.php.tests.reportPath=var/reports/phpunit.xml + +# Duplication exclusions +sonar.cpd.exclusions=migrations/** + +# ─── Rapports externes ──────────────────────────── +sonar.php.phpstan.reportPaths=var/reports/phpstan-report.json +sonar.javascript.eslint.reportPaths=var/reports/eslint-report.json +sonar.externalIssuesReportPaths=var/reports/semgrep-report.json +sonar.docker.hadolint.reportPaths=var/reports/hadolint-raw.json diff --git a/src/Command/CheckServicesCommand.php b/src/Command/CheckServicesCommand.php new file mode 100644 index 0000000..5a508ee --- /dev/null +++ b/src/Command/CheckServicesCommand.php @@ -0,0 +1,209 @@ +serviceRepository->createQueryBuilder('s') + ->where('s.url IS NOT NULL') + ->andWhere('s.url != :empty') + ->setParameter('empty', '') + ->getQuery() + ->getResult(); + + if (0 === \count($services)) { + $io->info('Aucun service a verifier.'); + + return Command::SUCCESS; + } + + $checked = 0; + $changed = 0; + + foreach ($services as $service) { + $oldStatus = $service->getStatus(); + + // Skip les services "soon" ou en maintenance manuelle + if (\in_array($oldStatus, [Service::STATUS_SOON, Service::STATUS_MAINTENANCE], true)) { + $io->comment('['.$service->getName().'] Skip ('.$oldStatus.')'); + continue; + } + + $newStatus = $this->checkService($service); + ++$checked; + + if ($newStatus !== $oldStatus) { + $message = 'Auto-detection: '.$oldStatus.' -> '.$newStatus; + $service->setStatus($newStatus, $message); + ++$changed; + $io->warning('['.$service->getName().'] '.$message); + } else { + $io->info('['.$service->getName().'] OK ('.$newStatus.')'); + } + } + + // Check Stripe via status page + $this->checkStripe($services, $io, $changed); + + $this->em->flush(); + + $io->success(sprintf('%d service(s) verifie(s), %d changement(s).', $checked, $changed)); + + return Command::SUCCESS; + } + + private function checkService(Service $service): string + { + $type = $service->getExternalType(); + + return match ($type) { + 'docuseal' => $this->checkDocuSeal($service), + 'vault' => $this->checkVault($service), + 'minio' => $this->checkMinio($service), + default => $this->checkHttp($service), + }; + } + + private function checkHttp(Service $service): string + { + try { + $response = $this->httpClient->request('GET', $service->getUrl(), [ + 'timeout' => 10, + 'max_redirects' => 3, + ]); + + $statusCode = $response->getStatusCode(); + + if ($statusCode >= 200 && $statusCode < 400) { + return Service::STATUS_UP; + } + + if ($statusCode >= 500) { + return Service::STATUS_DOWN; + } + + return Service::STATUS_DEGRADED; + } catch (\Throwable) { + return Service::STATUS_DOWN; + } + } + + private function checkDocuSeal(Service $service): string + { + try { + $response = $this->httpClient->request('GET', rtrim($service->getUrl(), '/').'/up', [ + 'timeout' => 10, + ]); + + return $response->getStatusCode() === 200 ? Service::STATUS_UP : Service::STATUS_DOWN; + } catch (\Throwable) { + return Service::STATUS_DOWN; + } + } + + private function checkVault(Service $service): string + { + try { + $response = $this->httpClient->request('GET', rtrim($service->getUrl(), '/').'/v1/sys/health', [ + 'timeout' => 10, + ]); + + $statusCode = $response->getStatusCode(); + + // Vault: 200 = initialized+unsealed, 429 = unsealed+standby, 472 = recovery, 501 = not initialized, 503 = sealed + if (200 === $statusCode) { + return Service::STATUS_UP; + } + + if (429 === $statusCode) { + return Service::STATUS_DEGRADED; + } + + return Service::STATUS_DOWN; + } catch (\Throwable) { + return Service::STATUS_DOWN; + } + } + + private function checkMinio(Service $service): string + { + try { + $response = $this->httpClient->request('GET', rtrim($service->getUrl(), '/').'/minio/health/live', [ + 'timeout' => 10, + ]); + + return $response->getStatusCode() === 200 ? Service::STATUS_UP : Service::STATUS_DOWN; + } catch (\Throwable) { + return Service::STATUS_DOWN; + } + } + + /** + * Check Stripe status via leur status page API. + * + * @param list $services + */ + private function checkStripe(array $services, SymfonyStyle $io, int &$changed): void + { + $stripeServices = array_filter($services, fn (Service $s) => 'stripe' === $s->getExternalType()); + + if ([] === $stripeServices) { + return; + } + + try { + $response = $this->httpClient->request('GET', 'https://status.stripe.com/api/v2/status.json', [ + 'timeout' => 10, + ]); + + $data = $response->toArray(false); + $indicator = $data['status']['indicator'] ?? 'none'; + + $status = match ($indicator) { + 'none' => Service::STATUS_UP, + 'minor' => Service::STATUS_DEGRADED, + 'major', 'critical' => Service::STATUS_DOWN, + default => Service::STATUS_UP, + }; + + $description = $data['status']['description'] ?? ''; + + foreach ($stripeServices as $service) { + $oldStatus = $service->getStatus(); + if ($status !== $oldStatus) { + $service->setStatus($status, 'Stripe: '.$description); + ++$changed; + $io->warning('[Stripe] '.$oldStatus.' -> '.$status.' ('.$description.')'); + } + } + } catch (\Throwable $e) { + $io->error('[Stripe] Impossible de verifier : '.$e->getMessage()); + } + } +} diff --git a/src/Command/CleanAttestationsCommand.php b/src/Command/CleanAttestationsCommand.php new file mode 100644 index 0000000..843c101 --- /dev/null +++ b/src/Command/CleanAttestationsCommand.php @@ -0,0 +1,131 @@ +docuSealApi = new Api($docuSealApiKey, rtrim($docuSealUrl, '/').'/api'); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + $expireDate = new \DateTimeImmutable('-7 days'); + + $attestations = $this->repository->createQueryBuilder('a') + ->where('a.createdAt < :expireDate') + ->setParameter('expireDate', $expireDate) + ->getQuery() + ->getResult(); + + if (0 === \count($attestations)) { + $io->success('Aucune attestation a supprimer.'); + + return Command::SUCCESS; + } + + $deleted = 0; + + foreach ($attestations as $attestation) { + // Supprimer les fichiers PDF locaux + $this->deleteFile($attestation->getPdfFileUnsigned()); + $this->deleteFile($attestation->getPdfFileSigned()); + $this->deleteFile($attestation->getPdfFileCertificate()); + + // Supprimer sur DocuSeal + $submitterId = $attestation->getSubmitterId(); + if (null !== $submitterId) { + $this->deleteFromDocuSeal($submitterId, $io); + } + + // Supprimer en BDD + $this->em->remove($attestation); + ++$deleted; + } + + $this->em->flush(); + + // Nettoyer les fichiers orphelins dans var/rgpd + $this->cleanOrphanFiles($expireDate, $io); + + $io->success(sprintf('%d attestation(s) supprimee(s).', $deleted)); + + return Command::SUCCESS; + } + + private function deleteFile(?string $path): void + { + if (null !== $path && file_exists($path)) { + @unlink($path); + } + } + + private function deleteFromDocuSeal(int $submitterId, SymfonyStyle $io): void + { + try { + $submitter = $this->docuSealApi->getSubmitter($submitterId); + $submissionId = $submitter['submission_id'] ?? null; + + if (null !== $submissionId) { + $this->docuSealApi->permanentlyDeleteSubmission($submissionId); + } + } catch (\Throwable $e) { + $io->warning('DocuSeal: impossible de supprimer le submitter '.$submitterId.': '.$e->getMessage()); + } + } + + private function cleanOrphanFiles(\DateTimeImmutable $expireDate, SymfonyStyle $io): void + { + $dirs = [ + $this->projectDir.'/var/rgpd', + $this->projectDir.'/var/rgpd/signed', + ]; + + $orphans = 0; + + foreach ($dirs as $dir) { + if (!is_dir($dir)) { + continue; + } + + foreach (new \DirectoryIterator($dir) as $file) { + if ($file->isDot() || $file->isDir()) { + continue; + } + + if ($file->getMTime() < $expireDate->getTimestamp()) { + @unlink($file->getPathname()); + ++$orphans; + } + } + } + + if ($orphans > 0) { + $io->note(sprintf('%d fichier(s) orphelin(s) supprime(s).', $orphans)); + } + } +} diff --git a/src/Command/MeilisearchSetupCommand.php b/src/Command/MeilisearchSetupCommand.php new file mode 100644 index 0000000..e9afc4a --- /dev/null +++ b/src/Command/MeilisearchSetupCommand.php @@ -0,0 +1,54 @@ +section('Configuration des index'); + $this->meilisearch->setupIndexes(); + $io->info('Index customer et reseller configures.'); + + $io->section('Indexation des clients'); + $customers = $this->customerRepository->findAll(); + foreach ($customers as $customer) { + $this->meilisearch->indexCustomer($customer); + } + $io->info(sprintf('%d client(s) indexe(s).', \count($customers))); + + $io->section('Indexation des revendeurs'); + $revendeurs = $this->revendeurRepository->findAll(); + foreach ($revendeurs as $revendeur) { + $this->meilisearch->indexRevendeur($revendeur); + } + $io->info(sprintf('%d revendeur(s) indexe(s).', \count($revendeurs))); + + $io->success('Meilisearch configure et donnees indexees.'); + + return Command::SUCCESS; + } +} diff --git a/src/Command/StripeSyncCommand.php b/src/Command/StripeSyncCommand.php new file mode 100644 index 0000000..5b5a44d --- /dev/null +++ b/src/Command/StripeSyncCommand.php @@ -0,0 +1,141 @@ +stripeSecretKey || 'sk_test_***' === $this->stripeSecretKey) { + $io->warning('Cle Stripe non configuree. Synchronisation ignoree.'); + + return Command::SUCCESS; + } + + \Stripe\Stripe::setApiKey($this->stripeSecretKey); + + $this->syncPayments($io); + $this->syncRefunds($io); + $this->syncPayouts($io); + $this->syncConnectAccounts($io); + + $io->success('Synchronisation Stripe terminee.'); + + return Command::SUCCESS; + } + + private function syncPayments(SymfonyStyle $io): void + { + $io->section('Paiements'); + + try { + $charges = \Stripe\Charge::all([ + 'limit' => 100, + 'created' => ['gte' => strtotime('-1 day')], + ]); + + $count = 0; + foreach ($charges->data as $charge) { + // TODO: sauvegarder en BDD (entity Payment) + ++$count; + } + + $io->info(sprintf('%d paiement(s) recupere(s) (dernières 24h).', $count)); + } catch (\Throwable $e) { + $io->error('Erreur paiements : '.$e->getMessage()); + } + } + + private function syncRefunds(SymfonyStyle $io): void + { + $io->section('Remboursements'); + + try { + $refunds = \Stripe\Refund::all([ + 'limit' => 100, + 'created' => ['gte' => strtotime('-1 day')], + ]); + + $count = 0; + foreach ($refunds->data as $refund) { + // TODO: sauvegarder en BDD (entity Refund) + ++$count; + } + + $io->info(sprintf('%d remboursement(s) recupere(s) (dernières 24h).', $count)); + } catch (\Throwable $e) { + $io->error('Erreur remboursements : '.$e->getMessage()); + } + } + + private function syncPayouts(SymfonyStyle $io): void + { + $io->section('Versements'); + + try { + $payouts = \Stripe\Payout::all([ + 'limit' => 100, + 'created' => ['gte' => strtotime('-7 days')], + ]); + + $count = 0; + foreach ($payouts->data as $payout) { + // TODO: sauvegarder en BDD (entity Payout) + ++$count; + } + + $io->info(sprintf('%d versement(s) recupere(s) (derniers 7 jours).', $count)); + } catch (\Throwable $e) { + $io->error('Erreur versements : '.$e->getMessage()); + } + } + + private function syncConnectAccounts(SymfonyStyle $io): void + { + $io->section('Comptes Connect'); + + try { + $accounts = \Stripe\Account::all(['limit' => 100]); + + $count = 0; + foreach ($accounts->data as $account) { + $status = $account->charges_enabled ? 'actif' : 'inactif'; + $payoutsEnabled = $account->payouts_enabled ? 'oui' : 'non'; + + $io->comment(sprintf( + '[%s] %s — Charges: %s, Payouts: %s', + $account->id, + $account->email ?? 'N/A', + $status, + $payoutsEnabled, + )); + + // TODO: mettre a jour le status dans l'entity Revendeur + ++$count; + } + + $io->info(sprintf('%d compte(s) Connect synchronise(s).', $count)); + } catch (\Throwable $e) { + $io->error('Erreur comptes Connect : '.$e->getMessage()); + } + } +} diff --git a/src/Controller/Admin/ClientsController.php b/src/Controller/Admin/ClientsController.php new file mode 100644 index 0000000..180938a --- /dev/null +++ b/src/Controller/Admin/ClientsController.php @@ -0,0 +1,159 @@ +findBy([], ['createdAt' => 'DESC']); + + return $this->render('admin/clients/index.html.twig', [ + 'customers' => $customers, + ]); + } + + #[Route('/create', name: 'create')] + public function create( + Request $request, + UserRepository $userRepository, + CustomerRepository $customerRepository, + EntityManagerInterface $em, + UserPasswordHasherInterface $passwordHasher, + MeilisearchService $meilisearch, + #[Autowire(env: 'STRIPE_SK')] string $stripeSecretKey, + ): Response { + if ('POST' === $request->getMethod()) { + $firstName = trim($request->request->getString('firstName')); + $lastName = trim($request->request->getString('lastName')); + $email = trim($request->request->getString('email')); + + if ('' === $firstName || '' === $lastName || '' === $email) { + $this->addFlash('error', 'Le prenom, nom et email sont requis.'); + + return $this->redirectToRoute('app_admin_clients_create'); + } + + if (null !== $userRepository->findOneBy(['email' => $email])) { + $this->addFlash('error', 'Un compte existe deja avec cet email.'); + + return $this->redirectToRoute('app_admin_clients_create'); + } + + try { + $tempPassword = bin2hex(random_bytes(8)); + + $user = new User(); + $user->setEmail($email); + $user->setFirstName($firstName); + $user->setLastName($lastName); + $user->setRoles(['ROLE_CUSTOMER']); + $user->setPassword($passwordHasher->hashPassword($user, $tempPassword)); + $user->setTempPassword($tempPassword); + $em->persist($user); + + $customer = new Customer($user); + $customer->setFirstName($firstName); + $customer->setLastName($lastName); + $customer->setEmail($email); + $customer->setPhone(trim($request->request->getString('phone')) ?: null); + $customer->setRaisonSociale(trim($request->request->getString('raisonSociale')) ?: null); + $customer->setSiret(trim($request->request->getString('siret')) ?: null); + $customer->setRcs(trim($request->request->getString('rcs')) ?: null); + $customer->setNumTva(trim($request->request->getString('numTva')) ?: null); + $customer->setAddress(trim($request->request->getString('address')) ?: null); + $customer->setAddress2(trim($request->request->getString('address2')) ?: null); + $customer->setZipCode(trim($request->request->getString('zipCode')) ?: null); + $customer->setCity(trim($request->request->getString('city')) ?: null); + $customer->setTypeCompany(trim($request->request->getString('typeCompany')) ?: null); + + // Creer le client Stripe + if ('' !== $stripeSecretKey && 'sk_test_***' !== $stripeSecretKey) { + \Stripe\Stripe::setApiKey($stripeSecretKey); + $stripeCustomer = \Stripe\Customer::create([ + 'email' => $email, + 'name' => $customer->getFullName(), + 'metadata' => ['crm_user_id' => 'pending'], + ]); + $customer->setStripeCustomerId($stripeCustomer->id); + } + + $em->persist($customer); + $em->flush(); + + // Mettre a jour le metadata Stripe avec l'ID reel + if (null !== $customer->getStripeCustomerId()) { + \Stripe\Customer::update($customer->getStripeCustomerId(), [ + 'metadata' => ['crm_user_id' => (string) $user->getId()], + ]); + } + + // Generer le code comptable + $codeComptable = $customerRepository->generateUniqueCodeComptable($customer); + $customer->setCodeComptable($codeComptable); + $em->flush(); + + // Indexer dans Meilisearch + try { + $meilisearch->indexCustomer($customer); + } catch (\Throwable) { + } + + $this->addFlash('success', 'Client '.$customer->getFullName().' cree.'); + + return $this->redirectToRoute('app_admin_clients_index'); + } catch (\Throwable $e) { + $this->addFlash('error', 'Erreur : '.$e->getMessage()); + } + } + + return $this->render('admin/clients/create.html.twig'); + } + + #[Route('/search', name: 'search', methods: ['GET'])] + public function search(Request $request, MeilisearchService $meilisearch): JsonResponse + { + $query = trim($request->query->getString('q')); + + if ('' === $query) { + return new JsonResponse([]); + } + + return new JsonResponse($meilisearch->searchCustomers($query)); + } + + #[Route('/{id}/toggle', name: 'toggle', methods: ['POST'])] + public function toggle(Customer $customer, EntityManagerInterface $em, MeilisearchService $meilisearch): Response + { + $newState = $customer->isActive() ? Customer::STATE_SUSPENDED : Customer::STATE_ACTIVE; + $customer->setState($newState); + $em->flush(); + + try { + $meilisearch->indexCustomer($customer); + } catch (\Throwable) { + } + + $this->addFlash('success', 'Client '.($customer->isActive() ? 'active' : 'suspendu').'.'); + + return $this->redirectToRoute('app_admin_clients_index'); + } +} diff --git a/src/Controller/Admin/DashboardController.php b/src/Controller/Admin/DashboardController.php new file mode 100644 index 0000000..152205e --- /dev/null +++ b/src/Controller/Admin/DashboardController.php @@ -0,0 +1,23 @@ +render('admin/dashboard.html.twig', [ + 'breadcrumbs' => [ + ['name' => 'Administration'], + ], + ]); + } +} diff --git a/src/Controller/Admin/FacturationController.php b/src/Controller/Admin/FacturationController.php new file mode 100644 index 0000000..dbd8529 --- /dev/null +++ b/src/Controller/Admin/FacturationController.php @@ -0,0 +1,19 @@ +render('admin/facturation/index.html.twig'); + } +} diff --git a/src/Controller/Admin/MembresController.php b/src/Controller/Admin/MembresController.php new file mode 100644 index 0000000..061b331 --- /dev/null +++ b/src/Controller/Admin/MembresController.php @@ -0,0 +1,192 @@ +listUsers(); + + foreach ($keycloakUsers as $kcUser) { + $email = $kcUser['email'] ?? ''; + $localUser = '' !== $email ? $userRepository->findOneBy(['email' => $email]) : null; + + $membres[] = [ + 'keycloakId' => $kcUser['id'], + 'email' => $email, + 'firstName' => $kcUser['firstName'] ?? '', + 'lastName' => $kcUser['lastName'] ?? '', + 'enabled' => $kcUser['enabled'] ?? false, + 'emailVerified' => $kcUser['emailVerified'] ?? false, + 'createdTimestamp' => $kcUser['createdTimestamp'] ?? 0, + 'existsInDb' => null !== $localUser, + 'localUser' => $localUser, + ]; + } + } catch (\Throwable) { + $this->addFlash('error', 'Impossible de recuperer la liste des membres depuis Keycloak.'); + } + + return $this->render('admin/membres.html.twig', [ + 'membres' => $membres, + ]); + } + + #[Route('/create', name: '_create', methods: ['POST'])] + public function create( + Request $request, + KeycloakAdminService $keycloak, + UserRepository $userRepository, + EntityManagerInterface $em, + UserPasswordHasherInterface $passwordHasher, + MailerService $mailer, + Environment $twig, + ): Response { + $firstName = trim($request->request->getString('firstName')); + $lastName = trim($request->request->getString('lastName')); + $email = trim($request->request->getString('email')); + $groups = $request->request->all('groups'); + + if ('' === $firstName || '' === $lastName || '' === $email) { + $this->addFlash('error', 'Veuillez remplir tous les champs.'); + + return $this->redirectToRoute('app_admin_membres'); + } + + // Verifier si l'email existe deja en BDD + if (null !== $userRepository->findOneBy(['email' => $email])) { + $this->addFlash('error', 'Un compte existe deja avec cet email.'); + + return $this->redirectToRoute('app_admin_membres'); + } + + try { + // 1. Creer le user dans Keycloak + $kcResult = $keycloak->createUser($email, $firstName, $lastName); + + if (!$kcResult['created'] || null === $kcResult['keycloakId']) { + $this->addFlash('error', 'Erreur lors de la creation du compte Keycloak. L\'email existe peut-etre deja.'); + + return $this->redirectToRoute('app_admin_membres'); + } + + $keycloakId = $kcResult['keycloakId']; + $tempPassword = $kcResult['tempPassword']; + + // 2. Ajouter aux groupes Keycloak + foreach ($groups as $group) { + $keycloak->addUserToGroup($keycloakId, $group); + } + + // 3. Creer le user en BDD locale + $user = new User(); + $user->setEmail($email); + $user->setFirstName($firstName); + $user->setLastName($lastName); + $user->setKeycloakId($keycloakId); + $user->setRoles(\in_array('super_admin_asso', $groups, true) ? ['ROLE_ROOT'] : ['ROLE_EMPLOYE']); + $user->setPassword($passwordHasher->hashPassword($user, $tempPassword)); + $user->setTempPassword($tempPassword); + + $em->persist($user); + $em->flush(); + + // 4. Envoyer le mail avec les identifiants + $mailer->sendEmail( + $email, + 'CRM Ecosplay - Votre compte a ete cree', + $twig->render('emails/membre_created.html.twig', [ + 'firstName' => $firstName, + 'lastName' => $lastName, + 'email' => $email, + 'tempPassword' => $tempPassword, + 'groups' => $groups, + ]), + null, + null, + false, + ); + + $this->addFlash('success', 'Le membre '.$firstName.' '.$lastName.' a ete cree. Un email avec les identifiants lui a ete envoye.'); + } catch (\Throwable $e) { + $this->addFlash('error', 'Erreur lors de la creation : '.$e->getMessage()); + } + + return $this->redirectToRoute('app_admin_membres'); + } + + #[Route('/{id}/resend', name: '_resend', methods: ['POST'])] + public function resend( + int $id, + UserRepository $userRepository, + MailerService $mailer, + Environment $twig, + ): Response { + $user = $userRepository->find($id); + + if (null === $user || !$user->hasTempPassword()) { + $this->addFlash('error', 'Impossible de renvoyer le mail. Le mot de passe temporaire n\'est plus disponible.'); + + return $this->redirectToRoute('app_admin_membres'); + } + + $mailer->sendEmail( + $user->getEmail(), + 'CRM Ecosplay - Rappel : votre compte a ete cree', + $twig->render('emails/membre_created.html.twig', [ + 'firstName' => $user->getFirstName(), + 'lastName' => $user->getLastName(), + 'email' => $user->getEmail(), + 'tempPassword' => $user->getTempPassword(), + 'groups' => [], + ]), + null, + null, + false, + ); + + $this->addFlash('success', 'Le mail de creation a ete renvoye a '.$user->getEmail().'.'); + + return $this->redirectToRoute('app_admin_membres'); + } + + #[Route('/{keycloakId}/delete', name: '_delete', methods: ['POST'])] + public function delete(string $keycloakId, KeycloakAdminService $keycloak, UserRepository $userRepository, EntityManagerInterface $em): Response + { + try { + $keycloak->deleteUser($keycloakId); + + $localUser = $userRepository->findOneBy(['keycloakId' => $keycloakId]); + if (null !== $localUser) { + $em->remove($localUser); + $em->flush(); + } + + $this->addFlash('success', 'Le membre a ete supprime.'); + } catch (\Throwable) { + $this->addFlash('error', 'Erreur lors de la suppression du membre.'); + } + + return $this->redirectToRoute('app_admin_membres'); + } +} diff --git a/src/Controller/Admin/ProfilController.php b/src/Controller/Admin/ProfilController.php new file mode 100644 index 0000000..391c858 --- /dev/null +++ b/src/Controller/Admin/ProfilController.php @@ -0,0 +1,147 @@ +render('admin/profil/index.html.twig'); + } + + #[Route('/password', name: '_password', methods: ['POST'])] + public function password( + Request $request, + UserPasswordHasherInterface $passwordHasher, + EntityManagerInterface $em, + KeycloakAdminService $keycloak, + ): Response { + /** @var User $user */ + $user = $this->getUser(); + + $currentPassword = $request->request->getString('current_password'); + $newPassword = $request->request->getString('new_password'); + $confirmPassword = $request->request->getString('confirm_password'); + + if (!$passwordHasher->isPasswordValid($user, $currentPassword)) { + $this->addFlash('error', 'Le mot de passe actuel est incorrect.'); + + return $this->redirectToRoute('app_admin_profil'); + } + + if (\strlen($newPassword) < 8) { + $this->addFlash('error', 'Le nouveau mot de passe doit contenir au moins 8 caracteres.'); + + return $this->redirectToRoute('app_admin_profil'); + } + + if ($newPassword !== $confirmPassword) { + $this->addFlash('error', 'Les mots de passe ne correspondent pas.'); + + return $this->redirectToRoute('app_admin_profil'); + } + + // Mettre à jour en BDD + $user->setPassword($passwordHasher->hashPassword($user, $newPassword)); + $user->clearTempPassword(); + $em->flush(); + + // Mettre à jour dans Keycloak si compte lié + if (null !== $user->getKeycloakId()) { + try { + $keycloak->resetPassword($user->getKeycloakId(), $newPassword); + } catch (\Throwable) { + // Best-effort + } + } + + $this->addFlash('success', 'Mot de passe modifie avec succes.'); + + return $this->redirectToRoute('app_admin_profil'); + } + + #[Route('/update', name: '_update', methods: ['POST'])] + #[IsGranted('ROLE_ROOT')] + public function update(Request $request, EntityManagerInterface $em, KeycloakAdminService $keycloak): Response + { + /** @var User $user */ + $user = $this->getUser(); + + $firstName = trim($request->request->getString('firstName')); + $lastName = trim($request->request->getString('lastName')); + $email = trim($request->request->getString('email')); + + if ('' === $firstName || '' === $lastName || '' === $email) { + $this->addFlash('error', 'Tous les champs sont requis.'); + + return $this->redirectToRoute('app_admin_profil'); + } + + $user->setFirstName($firstName); + $user->setLastName($lastName); + $user->setEmail($email); + $em->flush(); + + // Sync Keycloak + if (null !== $user->getKeycloakId()) { + try { + $keycloak->updateUser($user->getKeycloakId(), $firstName, $lastName, $email); + } catch (\Throwable) { + } + } + + $this->addFlash('success', 'Informations mises a jour.'); + + return $this->redirectToRoute('app_admin_profil'); + } + + #[Route('/avatar', name: '_avatar', methods: ['POST'])] + public function avatar(Request $request, EntityManagerInterface $em): Response + { + /** @var User $user */ + $user = $this->getUser(); + + $file = $request->files->get('avatar'); + + if (null === $file) { + $this->addFlash('error', 'Aucun fichier selectionne.'); + + return $this->redirectToRoute('app_admin_profil'); + } + + $user->setAvatarFile($file); + $em->flush(); + + $this->addFlash('success', 'Avatar mis a jour.'); + + return $this->redirectToRoute('app_admin_profil'); + } + + #[Route('/avatar/delete', name: '_avatar_delete', methods: ['POST'])] + public function avatarDelete(EntityManagerInterface $em): Response + { + /** @var User $user */ + $user = $this->getUser(); + + $user->setAvatarFile(null); + $user->setAvatar(null); + $em->flush(); + + $this->addFlash('success', 'Avatar supprime.'); + + return $this->redirectToRoute('app_admin_profil'); + } +} diff --git a/src/Controller/Admin/RevendeursController.php b/src/Controller/Admin/RevendeursController.php new file mode 100644 index 0000000..a8729f3 --- /dev/null +++ b/src/Controller/Admin/RevendeursController.php @@ -0,0 +1,208 @@ +findBy([], ['createdAt' => 'DESC']); + + return $this->render('admin/revendeurs/index.html.twig', [ + 'revendeurs' => $revendeurs, + ]); + } + + #[Route('/create', name: 'create')] + public function create( + Request $request, + RevendeurRepository $revendeurRepository, + UserRepository $userRepository, + EntityManagerInterface $em, + UserPasswordHasherInterface $passwordHasher, + MailerService $mailer, + Environment $twig, + ): Response { + if ('GET' === $request->getMethod()) { + return $this->render('admin/revendeurs/create.html.twig'); + } + + $firstName = trim($request->request->getString('firstName')); + $lastName = trim($request->request->getString('lastName')); + $email = trim($request->request->getString('email')); + $raisonSociale = trim($request->request->getString('raisonSociale')); + $siret = trim($request->request->getString('siret')); + $phone = trim($request->request->getString('phone')); + $isUseStripe = $request->request->getBoolean('isUseStripe'); + + if ('' === $firstName || '' === $lastName || '' === $email) { + $this->addFlash('error', 'Le prenom, nom et email sont requis.'); + + return $this->redirectToRoute('app_admin_revendeurs_index'); + } + + if (null !== $userRepository->findOneBy(['email' => $email])) { + $this->addFlash('error', 'Un compte existe deja avec cet email.'); + + return $this->redirectToRoute('app_admin_revendeurs_index'); + } + + try { + // Generer mot de passe temporaire + $tempPassword = bin2hex(random_bytes(8)); + + // Creer le user + $user = new User(); + $user->setEmail($email); + $user->setFirstName($firstName); + $user->setLastName($lastName); + $user->setRoles(['ROLE_REVENDEUR']); + $user->setPassword($passwordHasher->hashPassword($user, $tempPassword)); + $user->setTempPassword($tempPassword); + + $em->persist($user); + + // Creer le revendeur + $code = $revendeurRepository->generateUniqueCode(); + $revendeur = new Revendeur($user, $code); + $revendeur->setRaisonSociale('' !== $raisonSociale ? $raisonSociale : null); + $revendeur->setSiret('' !== $siret ? $siret : null); + $revendeur->setEmail($email); + $revendeur->setPhone('' !== $phone ? $phone : null); + $revendeur->setAddress(trim($request->request->getString('address')) ?: null); + $revendeur->setZipCode(trim($request->request->getString('zipCode')) ?: null); + $revendeur->setCity(trim($request->request->getString('city')) ?: null); + $revendeur->setIsUseStripe($isUseStripe); + + $em->persist($revendeur); + $em->flush(); + + // Envoyer le mail + $setPasswordUrl = $this->generateUrl('app_set_password', [ + 'token' => $tempPassword, + ], UrlGeneratorInterface::ABSOLUTE_URL); + + $mailer->sendEmail( + $email, + 'CRM Ecosplay - Bienvenue dans l\'espace revendeur', + $twig->render('emails/revendeur_created.html.twig', [ + 'firstName' => $firstName, + 'lastName' => $lastName, + 'email' => $email, + 'codeRevendeur' => $code, + 'setPasswordUrl' => $setPasswordUrl, + ]), + null, + null, + false, + ); + + $this->addFlash('success', 'Revendeur '.$firstName.' '.$lastName.' cree (Code: '.$code.').'); + } catch (\Throwable $e) { + $this->addFlash('error', 'Erreur lors de la creation : '.$e->getMessage()); + } + + return $this->redirectToRoute('app_admin_revendeurs_index'); + } + + #[Route('/search', name: 'search', methods: ['GET'])] + public function search(Request $request, MeilisearchService $meilisearch): \Symfony\Component\HttpFoundation\JsonResponse + { + $query = trim($request->query->getString('q')); + + if ('' === $query) { + return new \Symfony\Component\HttpFoundation\JsonResponse([]); + } + + return new \Symfony\Component\HttpFoundation\JsonResponse($meilisearch->searchRevendeurs($query)); + } + + #[Route('/{id}/toggle', name: 'toggle', methods: ['POST'])] + public function toggle(Revendeur $revendeur, EntityManagerInterface $em): Response + { + $revendeur->setIsActive(!$revendeur->isActive()); + $revendeur->setUpdatedAt(new \DateTimeImmutable()); + $em->flush(); + + $this->addFlash('success', 'Revendeur '.$revendeur->getCodeRevendeur().' '.($revendeur->isActive() ? 'active' : 'desactive').'.'); + + return $this->redirectToRoute('app_admin_revendeurs_index'); + } + + #[Route('/{id}/edit', name: 'edit')] + public function edit(Revendeur $revendeur, Request $request, EntityManagerInterface $em, MeilisearchService $meilisearch): Response + { + if ('POST' === $request->getMethod()) { + $revendeur->setRaisonSociale(trim($request->request->getString('raisonSociale')) ?: null); + $revendeur->setSiret(trim($request->request->getString('siret')) ?: null); + $revendeur->setEmail(trim($request->request->getString('email')) ?: null); + $revendeur->setPhone(trim($request->request->getString('phone')) ?: null); + $revendeur->setAddress(trim($request->request->getString('address')) ?: null); + $revendeur->setZipCode(trim($request->request->getString('zipCode')) ?: null); + $revendeur->setCity(trim($request->request->getString('city')) ?: null); + $revendeur->setIsUseStripe($request->request->getBoolean('isUseStripe')); + $revendeur->setUpdatedAt(new \DateTimeImmutable()); + $em->flush(); + + try { + $meilisearch->indexRevendeur($revendeur); + } catch (\Throwable) { + } + + $this->addFlash('success', 'Revendeur '.$revendeur->getCodeRevendeur().' mis a jour.'); + + return $this->redirectToRoute('app_admin_revendeurs_index'); + } + + return $this->render('admin/revendeurs/edit.html.twig', [ + 'revendeur' => $revendeur, + ]); + } + + #[Route('/{id}/contrat', name: 'contrat', methods: ['GET'])] + public function contrat( + Revendeur $revendeur, + \Twig\Environment $twig, + #[Autowire('%kernel.project_dir%')] string $projectDir, + ): Response { + $logoPath = $projectDir.'/public/logo.jpg'; + $logo = file_exists($logoPath) ? 'data:image/jpeg;base64,'.base64_encode(file_get_contents($logoPath)) : ''; + + $html = $twig->render('pdf/contrat_revendeur.html.twig', [ + 'revendeur' => $revendeur, + 'logo' => $logo, + 'date' => new \DateTimeImmutable(), + ]); + + $dompdf = new Dompdf(); + $dompdf->loadHtml($html); + $dompdf->setPaper('A4'); + $dompdf->render(); + + return new Response($dompdf->output(), 200, [ + 'Content-Type' => 'application/pdf', + 'Content-Disposition' => 'inline; filename="contrat-'.$revendeur->getCodeRevendeur().'.pdf"', + ]); + } +} diff --git a/src/Controller/Admin/ServicesController.php b/src/Controller/Admin/ServicesController.php new file mode 100644 index 0000000..fc42390 --- /dev/null +++ b/src/Controller/Admin/ServicesController.php @@ -0,0 +1,19 @@ +render('admin/services/index.html.twig'); + } +} diff --git a/src/Controller/Admin/StatsController.php b/src/Controller/Admin/StatsController.php new file mode 100644 index 0000000..29bbaa1 --- /dev/null +++ b/src/Controller/Admin/StatsController.php @@ -0,0 +1,140 @@ +query->getString('period', 'current'); + $now = new \DateTimeImmutable(); + + if ('custom' === $period) { + $dateFrom = $request->query->getString('from', $now->format('Y-m-01')); + $dateTo = $request->query->getString('to', $now->format('Y-m-d')); + } elseif ('current' === $period) { + $dateFrom = $now->format('Y-m-01'); + $dateTo = $now->format('Y-m-d'); + } else { + $months = (int) $period; + $dateFrom = $now->modify('-'.$months.' months')->format('Y-m-01'); + $dateTo = $now->format('Y-m-d'); + } + + // Donnees fictives + $globalStats = [ + 'ca_ht' => 12_450.00, + 'ca_tva' => 2_490.00, + 'ca_ttc' => 14_940.00, + 'cout_total' => 220.00, + 'commission_stripe' => 186.75, + ]; + + $servicesStats = [ + [ + 'name' => 'Site Internet', + 'slug' => 'site-internet', + 'color' => '#fabf04', + 'ca_ht' => 4_200.00, + 'ca_tva' => 840.00, + 'ca_ttc' => 5_040.00, + 'cout' => 150.00, + 'clients' => 12, + 'abonnements' => 12, + ], + [ + 'name' => 'E-Ticket', + 'slug' => 'e-ticket', + 'color' => '#4338ca', + 'ca_ht' => 3_800.00, + 'ca_tva' => 760.00, + 'ca_ttc' => 4_560.00, + 'cout' => 0.00, + 'clients' => 8, + 'abonnements' => 8, + ], + [ + 'name' => 'E-Asso', + 'slug' => 'e-asso', + 'color' => '#16a34a', + 'ca_ht' => 1_750.00, + 'ca_tva' => 350.00, + 'ca_ttc' => 2_100.00, + 'cout' => 0.00, + 'clients' => 7, + 'abonnements' => 7, + ], + [ + 'name' => 'E-Mail', + 'slug' => 'e-mail', + 'color' => '#dc2626', + 'ca_ht' => 850.00, + 'ca_tva' => 170.00, + 'ca_ttc' => 1_020.00, + 'cout' => 70.00, + 'clients' => 15, + 'abonnements' => 42, + ], + [ + 'name' => 'E-Newsletter', + 'slug' => 'e-newsletter', + 'color' => '#ea580c', + 'ca_ht' => 1_100.00, + 'ca_tva' => 220.00, + 'ca_ttc' => 1_320.00, + 'cout' => 0.00, + 'clients' => 4, + 'abonnements' => 4, + ], + [ + 'name' => 'E-Sign', + 'slug' => 'e-sign', + 'color' => '#7c3aed', + 'ca_ht' => 400.00, + 'ca_tva' => 80.00, + 'ca_ttc' => 480.00, + 'cout' => 0.00, + 'clients' => 2, + 'abonnements' => 2, + ], + [ + 'name' => 'E-Ndd', + 'slug' => 'e-ndd', + 'color' => '#0891b2', + 'ca_ht' => 350.00, + 'ca_tva' => 70.00, + 'ca_ttc' => 420.00, + 'clients' => 8, + 'abonnements' => 8, + ], + ]; + + // Evolution mensuelle fictive (6 derniers mois) + $monthlyEvolution = [ + ['month' => 'Oct 2025', 'ca_ht' => 9_800], + ['month' => 'Nov 2025', 'ca_ht' => 10_200], + ['month' => 'Dec 2025', 'ca_ht' => 11_500], + ['month' => 'Jan 2026', 'ca_ht' => 10_900], + ['month' => 'Fev 2026', 'ca_ht' => 11_800], + ['month' => 'Mar 2026', 'ca_ht' => 12_450], + ]; + + return $this->render('admin/stats/index.html.twig', [ + 'period' => $period, + 'dateFrom' => $dateFrom, + 'dateTo' => $dateTo, + 'global' => $globalStats, + 'services' => $servicesStats, + 'evolution' => $monthlyEvolution, + ]); + } +} diff --git a/src/Controller/Admin/StatusController.php b/src/Controller/Admin/StatusController.php new file mode 100644 index 0000000..85e9d8e --- /dev/null +++ b/src/Controller/Admin/StatusController.php @@ -0,0 +1,231 @@ +findBy([], ['position' => 'ASC']); + + $servicesData = []; + foreach ($categories as $category) { + $categoryData = [ + 'category' => $category, + 'services' => [], + ]; + + foreach ($category->getServices() as $service) { + $history = $serviceRepository->getHistoryForDays($service, 30); + $dailyStatus = $serviceRepository->getDailyStatus($service, 30); + $uptime = $service->computeUptimeRatio($history, 30); + + $categoryData['services'][] = [ + 'service' => $service, + 'uptime' => $uptime, + 'dailyStatus' => $dailyStatus, + ]; + } + + $servicesData[] = $categoryData; + } + + $activeMessages = $em->getRepository(ServiceMessage::class)->findBy( + ['isActive' => true], + ['createdAt' => 'DESC'], + ); + + $allServices = $serviceRepository->findBy([], ['name' => 'ASC']); + + return $this->render('admin/status/index.html.twig', [ + 'servicesData' => $servicesData, + 'activeMessages' => $activeMessages, + 'allServices' => $allServices, + ]); + } + + #[Route('/manage', name: 'manage')] + public function manage(ServiceCategoryRepository $categoryRepository): Response + { + $categories = $categoryRepository->findBy([], ['position' => 'ASC']); + + return $this->render('admin/status/manage.html.twig', [ + 'categories' => $categories, + ]); + } + + #[Route('/category/create', name: 'category_create', methods: ['POST'])] + public function categoryCreate(Request $request, EntityManagerInterface $em, SluggerInterface $slugger): Response + { + $name = trim($request->request->getString('name')); + + if ('' === $name) { + $this->addFlash('error', 'Le nom de la categorie est requis.'); + + return $this->redirectToRoute('app_admin_status_manage'); + } + + $slug = strtolower($slugger->slug($name)->toString()); + $category = new ServiceCategory($name, $slug); + $category->setPosition($request->request->getInt('position', 0)); + + $em->persist($category); + $em->flush(); + + $this->addFlash('success', 'Categorie "'.$name.'" creee.'); + + return $this->redirectToRoute('app_admin_status_manage'); + } + + #[Route('/category/{id}/delete', name: 'category_delete', methods: ['POST'])] + public function categoryDelete(ServiceCategory $category, EntityManagerInterface $em): Response + { + $name = $category->getName(); + $em->remove($category); + $em->flush(); + + $this->addFlash('success', 'Categorie "'.$name.'" supprimee.'); + + return $this->redirectToRoute('app_admin_status_manage'); + } + + #[Route('/service/create', name: 'service_create', methods: ['POST'])] + public function serviceCreate(Request $request, EntityManagerInterface $em, ServiceCategoryRepository $categoryRepository, SluggerInterface $slugger): Response + { + $name = trim($request->request->getString('name')); + $url = trim($request->request->getString('url')); + $categoryId = $request->request->getInt('category_id'); + + if ('' === $name || 0 === $categoryId) { + $this->addFlash('error', 'Le nom et la categorie sont requis.'); + + return $this->redirectToRoute('app_admin_status_manage'); + } + + $category = $categoryRepository->find($categoryId); + if (null === $category) { + $this->addFlash('error', 'Categorie introuvable.'); + + return $this->redirectToRoute('app_admin_status_manage'); + } + + $externalType = trim($request->request->getString('external_type')); + + $slug = strtolower($slugger->slug($name)->toString()); + $service = new Service($name, $slug, $category); + + if ('' !== $url) { + $service->setUrl($url); + } + + if ('' !== $externalType) { + $service->setIsExternal(true); + $service->setExternalType($externalType); + } + + $service->setPosition($request->request->getInt('position', 0)); + + $em->persist($service); + $em->flush(); + + $this->addFlash('success', 'Service "'.$name.'" cree.'); + + return $this->redirectToRoute('app_admin_status_manage'); + } + + #[Route('/service/{id}/delete', name: 'service_delete', methods: ['POST'])] + public function serviceDelete(Service $service, EntityManagerInterface $em): Response + { + $name = $service->getName(); + $em->remove($service); + $em->flush(); + + $this->addFlash('success', 'Service "'.$name.'" supprime.'); + + return $this->redirectToRoute('app_admin_status_manage'); + } + + #[Route('/{id}/update', name: 'update', methods: ['POST'])] + public function update(Service $service, Request $request, EntityManagerInterface $em): Response + { + $status = $request->request->getString('status'); + $message = trim($request->request->getString('message')); + + if (!\in_array($status, Service::STATUSES, true)) { + $this->addFlash('error', 'Statut invalide.'); + + return $this->redirectToRoute('app_admin_status_index'); + } + + $service->setStatus($status, '' !== $message ? $message : null); + $em->flush(); + + $this->addFlash('success', 'Statut de "'.$service->getName().'" mis a jour.'); + + return $this->redirectToRoute('app_admin_status_index'); + } + + #[Route('/message/create', name: 'message_create', methods: ['POST'])] + public function messageCreate(Request $request, EntityManagerInterface $em, ServiceRepository $serviceRepository): Response + { + $serviceId = $request->request->getInt('service_id'); + $title = trim($request->request->getString('title')); + $content = trim($request->request->getString('content')); + $severity = $request->request->getString('severity', 'info'); + + if (0 === $serviceId || '' === $title || '' === $content) { + $this->addFlash('error', 'Tous les champs sont requis.'); + + return $this->redirectToRoute('app_admin_status_index'); + } + + $service = $serviceRepository->find($serviceId); + if (null === $service) { + $this->addFlash('error', 'Service introuvable.'); + + return $this->redirectToRoute('app_admin_status_index'); + } + + $message = new ServiceMessage($service, $title, $content, $severity, $this->getUser()); + $em->persist($message); + $em->flush(); + + $this->addFlash('success', 'Message ajoute sur "'.$service->getName().'".'); + + return $this->redirectToRoute('app_admin_status_index'); + } + + #[Route('/message/{id}/resolve', name: 'message_resolve', methods: ['POST'])] + public function messageResolve(ServiceMessage $message, EntityManagerInterface $em): Response + { + $message->resolve(); + $em->flush(); + + $this->addFlash('success', 'Message resolu.'); + + return $this->redirectToRoute('app_admin_status_index'); + } + + #[Route('/api/{id}/daily', name: 'api_daily', methods: ['GET'])] + public function apiDaily(Service $service, ServiceRepository $serviceRepository): JsonResponse + { + return new JsonResponse($serviceRepository->getDailyStatus($service, 30)); + } +} diff --git a/src/Controller/Admin/SyncController.php b/src/Controller/Admin/SyncController.php new file mode 100644 index 0000000..24aae00 --- /dev/null +++ b/src/Controller/Admin/SyncController.php @@ -0,0 +1,83 @@ +render('admin/sync/index.html.twig', [ + 'totalCustomers' => $customerRepository->count([]), + 'totalRevendeurs' => $revendeurRepository->count([]), + ]); + } + + #[Route('/customers', name: 'customers', methods: ['POST'])] + public function syncCustomers(CustomerRepository $customerRepository, MeilisearchService $meilisearch): Response + { + try { + $meilisearch->setupIndexes(); + $customers = $customerRepository->findAll(); + foreach ($customers as $customer) { + $meilisearch->indexCustomer($customer); + } + $this->addFlash('success', \count($customers).' client(s) synchronise(s) dans Meilisearch.'); + } catch (\Throwable $e) { + $this->addFlash('error', 'Erreur sync clients : '.$e->getMessage()); + } + + return $this->redirectToRoute('app_admin_sync_index'); + } + + #[Route('/revendeurs', name: 'revendeurs', methods: ['POST'])] + public function syncRevendeurs(RevendeurRepository $revendeurRepository, MeilisearchService $meilisearch): Response + { + try { + $meilisearch->setupIndexes(); + $revendeurs = $revendeurRepository->findAll(); + foreach ($revendeurs as $revendeur) { + $meilisearch->indexRevendeur($revendeur); + } + $this->addFlash('success', \count($revendeurs).' revendeur(s) synchronise(s) dans Meilisearch.'); + } catch (\Throwable $e) { + $this->addFlash('error', 'Erreur sync revendeurs : '.$e->getMessage()); + } + + return $this->redirectToRoute('app_admin_sync_index'); + } + + #[Route('/all', name: 'all', methods: ['POST'])] + public function syncAll(CustomerRepository $customerRepository, RevendeurRepository $revendeurRepository, MeilisearchService $meilisearch): Response + { + try { + $meilisearch->setupIndexes(); + + $customers = $customerRepository->findAll(); + foreach ($customers as $customer) { + $meilisearch->indexCustomer($customer); + } + + $revendeurs = $revendeurRepository->findAll(); + foreach ($revendeurs as $revendeur) { + $meilisearch->indexRevendeur($revendeur); + } + + $this->addFlash('success', \count($customers).' client(s) et '.\count($revendeurs).' revendeur(s) synchronise(s).'); + } catch (\Throwable $e) { + $this->addFlash('error', 'Erreur sync : '.$e->getMessage()); + } + + return $this->redirectToRoute('app_admin_sync_index'); + } +} diff --git a/src/Controller/AnalyticsController.php b/src/Controller/AnalyticsController.php new file mode 100644 index 0000000..ae8c578 --- /dev/null +++ b/src/Controller/AnalyticsController.php @@ -0,0 +1,112 @@ +getContent(), true); + + if (!hash_equals($expectedToken, $token) || !$envelope || !isset($envelope['d'])) { + return new Response('', !hash_equals($expectedToken, $token) ? 404 : 400); + } + + $data = $crypto->decrypt($envelope['d']); + if (null === $data) { + return new Response('', 403); + } + + return $this->handleTrackData($data, $request, $crypto, $em, $bus); + } + + /** @param array $data */ + private function handleTrackData( + array $data, + Request $request, + AnalyticsCryptoService $crypto, + EntityManagerInterface $em, + MessageBusInterface $bus, + ): Response { + $uid = $data['uid'] ?? null; + + if (!$uid) { + $visitor = $this->createVisitor($request, $data, $crypto, $em); + + return new JsonResponse(['d' => $crypto->encrypt([ + 'uid' => $visitor->getUid(), + 'h' => $visitor->getHash(), + ])]); + } + + if (!($data['h'] ?? null) || !$crypto->verifyVisitorHash($uid, $data['h'])) { + return new Response('', 403); + } + + if (isset($data['setUser'])) { + $bus->dispatch(new AnalyticsMessage($uid, 'set_user', ['userId' => (int) $data['setUser']])); + } else { + $bus->dispatch(new AnalyticsMessage($uid, 'page_view', [ + 'url' => $data['u'] ?? '/', + 'title' => $data['t'] ?? null, + 'referrer' => $data['r'] ?? null, + ])); + } + + return new Response('', 204); + } + + /** @param array $data */ + private function createVisitor( + Request $request, + array $data, + AnalyticsCryptoService $crypto, + EntityManagerInterface $em, + ): AnalyticsUniqId { + $uid = sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x', + random_int(0, 0xFFFF), random_int(0, 0xFFFF), + random_int(0, 0xFFFF), + random_int(0, 0x0FFF) | 0x4000, + random_int(0, 0x3FFF) | 0x8000, + random_int(0, 0xFFFF), random_int(0, 0xFFFF), random_int(0, 0xFFFF), + ); + $ua = $request->headers->get('User-Agent', ''); + + $visitor = new AnalyticsUniqId(); + $visitor->setUid($uid); + $visitor->setHash($crypto->generateVisitorHash($uid)); + $visitor->setIpHash(hash('sha256', $request->getClientIp() ?? '')); + $visitor->setUserAgent(substr($ua, 0, 512)); + $visitor->setScreenWidth(isset($data['sw']) ? (int) $data['sw'] : null); + $visitor->setScreenHeight(isset($data['sh']) ? (int) $data['sh'] : null); + $visitor->setLanguage(isset($data['l']) ? substr((string) $data['l'], 0, 10) : null); + $visitor->setDeviceType(AnalyticsUniqId::parseDeviceType($ua)); + $visitor->setOs(AnalyticsUniqId::parseOs($ua)); + $visitor->setBrowser(AnalyticsUniqId::parseBrowser($ua)); + + $em->persist($visitor); + $em->flush(); + + return $visitor; + } +} diff --git a/src/Controller/AttestationController.php b/src/Controller/AttestationController.php new file mode 100644 index 0000000..7975ea6 --- /dev/null +++ b/src/Controller/AttestationController.php @@ -0,0 +1,86 @@ +findOneBy(['reference' => $reference]); + + if (null === $attestation) { + return $this->render('attestation/not_found.html.twig', [ + 'reference' => $reference, + ]); + } + + $valid = $attestation->verify($hmacSecret); + + return $this->render('attestation/verify.html.twig', [ + 'attestation' => $attestation, + 'valid' => $valid, + 'breadcrumbs' => [ + ['name' => 'Accueil', 'url' => '/'], + ['name' => 'Verification attestation'], + ], + ]); + } + + #[Route('/attestation/{reference}/download', name: 'app_attestation_download', methods: ['GET'])] + public function download( + string $reference, + AttestationRepository $repository, + ): Response { + $attestation = $repository->findOneBy(['reference' => $reference]); + + if (null === $attestation) { + throw $this->createNotFoundException('Attestation introuvable.'); + } + + $path = $attestation->getPdfFileSigned() ?? $attestation->getPdfFileUnsigned(); + + if (null === $path || !file_exists($path)) { + throw $this->createNotFoundException('Le fichier PDF n\'est pas disponible.'); + } + + $response = new BinaryFileResponse($path); + $response->setContentDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT, 'attestation-'.$attestation->getReference().'.pdf'); + + return $response; + } + + #[Route('/attestation/{reference}/audit', name: 'app_attestation_audit', methods: ['GET'])] + public function audit( + string $reference, + AttestationRepository $repository, + ): Response { + $attestation = $repository->findOneBy(['reference' => $reference]); + + if (null === $attestation) { + throw $this->createNotFoundException('Attestation introuvable.'); + } + + $path = $attestation->getPdfFileCertificate(); + + if (null === $path || !file_exists($path)) { + throw $this->createNotFoundException('Le certificat de signature n\'est pas disponible.'); + } + + $response = new BinaryFileResponse($path); + $response->setContentDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT, 'certificat-'.$attestation->getReference().'.pdf'); + + return $response; + } +} diff --git a/src/Controller/CspReportController.php b/src/Controller/CspReportController.php new file mode 100644 index 0000000..6d1f3d0 --- /dev/null +++ b/src/Controller/CspReportController.php @@ -0,0 +1,90 @@ +getContent(); + $report = empty($data) ? null : json_decode($data, true); + + if (null === $report || JSON_ERROR_NONE !== json_last_error()) { + $logger->error('CSP Report: invalid or empty payload'); + + return new Response('Invalid request', 400); + } + + $details = $report['csp-report'] ?? $report; + + $sourceFile = $details['source-file'] ?? ''; + $blockedUri = $details['blocked-uri'] ?? ''; + $documentUri = $details['document-uri'] ?? ''; + $violatedDirective = $details['violated-directive'] ?? ''; + + if ($this->isIgnoredViolation($sourceFile, $blockedUri, $documentUri)) { + $logger->info('CSP Violation ignored (Extension or Local Dev): '.$sourceFile); + + return new Response('Report ignored', 204); + } + + $logger->warning('REAL CSP VIOLATION: '.$data); + $this->sendAlert($mailer, $logger, $report, $documentUri, $violatedDirective, $blockedUri, $sourceFile); + + return new Response('Report processed', 204); + } + + private function isIgnoredViolation(string $sourceFile, string $blockedUri, string $documentUri): bool + { + return str_contains($sourceFile, 'extension://') + || str_contains($sourceFile, 'chrome-extension') + || str_contains($sourceFile, 'moz-extension') + || str_contains($documentUri, '.local') + || str_contains($sourceFile, 'localhost') + || 'wasm-eval' === $blockedUri + || 'inline' === $blockedUri && str_contains($sourceFile, 'node_modules') + || 'about:blank' === $blockedUri; + } + + /** + * @param array $report + */ + private function sendAlert(MailerInterface $mailer, LoggerInterface $logger, array $report, string $documentUri, string $violatedDirective, string $blockedUri, string $sourceFile): void + { + $email = (new Email()) + ->from('security-notify@e-cosplay.fr') + ->to($this->getParameter('admin_email')) + ->subject('Alerte Securite : Violation CSP detectee') + ->priority(Email::PRIORITY_HIGH) + ->text( + "Un rapport de violation CSP potentiellement critique a ete intercepte.\n\n". + 'Document: '.$documentUri."\n". + 'Directive violee: '.$violatedDirective."\n". + 'Element bloque: '.$blockedUri."\n". + 'Fichier source: '.$sourceFile."\n\n". + "Details complets :\n". + json_encode($report, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) + ); + + try { + $mailer->send($email); + } catch (\Exception $e) { + $logger->error('Failed to send CSP report email: '.$e->getMessage()); + } + } +} diff --git a/src/Controller/EmailTrackingController.php b/src/Controller/EmailTrackingController.php new file mode 100644 index 0000000..6b32e32 --- /dev/null +++ b/src/Controller/EmailTrackingController.php @@ -0,0 +1,105 @@ +findOneBy(['messageId' => $messageId]); + + if (null !== $tracking) { + $tracking->markAsOpened(); + $em->flush(); + } + + $response = new BinaryFileResponse($projectDir.'/public/logo.jpg'); + $response->headers->set('Content-Type', 'image/jpeg'); + $response->headers->set('Cache-Control', 'no-store'); + + return $response; + } + + #[Route('/email/{messageId}', name: 'app_email_view', methods: ['GET'])] + public function view( + string $messageId, + EmailTrackingRepository $repository, + ): Response { + $tracking = $repository->findOneBy(['messageId' => $messageId]); + + if (null === $tracking || null === $tracking->getHtmlBody()) { + throw $this->createNotFoundException('Email introuvable.'); + } + + $html = $tracking->getHtmlBody(); + $attachments = $tracking->getAttachments(); + + if ($attachments) { + $attachmentHtml = ''; + $attachmentHtml .= ''; + + foreach ($attachments as $index => $attachment) { + $name = $attachment['name'] ?? basename($attachment['path']); + $downloadUrl = $this->generateUrl('app_email_attachment', [ + 'messageId' => $messageId, + 'index' => $index, + ]); + $attachmentHtml .= ''; + } + + $attachmentHtml .= '
Pieces jointes
'; + $attachmentHtml .= ''; + $attachmentHtml .= '📎 '.htmlspecialchars($name).''; + $attachmentHtml .= '
'; + $html = str_replace('', $attachmentHtml.'', $html); + } + + return new Response($html); + } + + #[Route('/email/{messageId}/attachment/{index}', name: 'app_email_attachment', methods: ['GET'])] + public function attachment( + string $messageId, + int $index, + EmailTrackingRepository $repository, + ): Response { + $tracking = $repository->findOneBy(['messageId' => $messageId]); + + if (null === $tracking) { + throw $this->createNotFoundException('Email introuvable.'); + } + + $attachments = $tracking->getAttachments(); + + if (null === $attachments || !isset($attachments[$index])) { + throw $this->createNotFoundException('Piece jointe introuvable.'); + } + + $attachment = $attachments[$index]; + $path = $attachment['path']; + $name = $attachment['name'] ?? basename($path); + + if (!file_exists($path)) { + throw $this->createNotFoundException('Le fichier n\'est plus disponible.'); + } + + $response = new BinaryFileResponse($path); + $response->setContentDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT, $name); + + return $response; + } +} diff --git a/src/Controller/EspaceClientController.php b/src/Controller/EspaceClientController.php new file mode 100644 index 0000000..7722e5f --- /dev/null +++ b/src/Controller/EspaceClientController.php @@ -0,0 +1,22 @@ +render('espace_client/index.html.twig', [ + 'breadcrumbs' => [ + ['name' => 'Accueil', 'url' => '/'], + ['name' => 'Espace Client'], + ], + ]); + } +} diff --git a/src/Controller/EspacePrestataireController.php b/src/Controller/EspacePrestataireController.php new file mode 100644 index 0000000..ba41304 --- /dev/null +++ b/src/Controller/EspacePrestataireController.php @@ -0,0 +1,22 @@ +render('espace_prestataire/index.html.twig', [ + 'breadcrumbs' => [ + ['name' => 'Accueil', 'url' => '/'], + ['name' => 'Espace Prestataire'], + ], + ]); + } +} diff --git a/src/Controller/ExternalRedirectController.php b/src/Controller/ExternalRedirectController.php new file mode 100644 index 0000000..bc93222 --- /dev/null +++ b/src/Controller/ExternalRedirectController.php @@ -0,0 +1,21 @@ +query->getString('redirUrl', ''); + + return $this->render('external_redirect.html.twig', [ + 'url' => $url, + ]); + } +} diff --git a/src/Controller/ForgotPasswordController.php b/src/Controller/ForgotPasswordController.php new file mode 100644 index 0000000..5e40cbe --- /dev/null +++ b/src/Controller/ForgotPasswordController.php @@ -0,0 +1,117 @@ +getSession()->get('reset_step', 'email'); + $error = null; + $success = null; + + if ('POST' === $request->getMethod()) { + $action = $request->request->getString('action'); + + if ('send_code' === $action) { + $email = trim($request->request->getString('email')); + $user = $userRepository->findOneBy(['email' => $email]); + + // Toujours afficher le meme message pour ne pas reveler si le compte existe + $code = str_pad((string) random_int(0, 999999), 6, '0', STR_PAD_LEFT); + $request->getSession()->set('reset_code', $code); + $request->getSession()->set('reset_email', $email); + $request->getSession()->set('reset_expires', time() + 600); // 10 min + $request->getSession()->set('reset_step', 'code'); + + if (null !== $user) { + $mailer->sendEmail( + $email, + 'CRM Ecosplay - Code de reinitialisation', + $twig->render('emails/forgot_password_code.html.twig', [ + 'code' => $code, + ]), + null, + null, + false, + ); + } + + $success = 'Si un compte existe avec cette adresse, un code vous a ete envoye par email.'; + $step = 'code'; + } elseif ('reset' === $action) { + $inputCode = trim($request->request->getString('code')); + $newPassword = $request->request->getString('password'); + $sessionCode = $request->getSession()->get('reset_code'); + $sessionEmail = $request->getSession()->get('reset_email'); + $expires = $request->getSession()->get('reset_expires', 0); + + if (time() > $expires) { + $error = 'Le code a expire. Veuillez recommencer.'; + $request->getSession()->remove('reset_step'); + $request->getSession()->remove('reset_code'); + $request->getSession()->remove('reset_email'); + $request->getSession()->remove('reset_expires'); + $step = 'email'; + } elseif (!hash_equals($sessionCode, $inputCode)) { + $error = 'Le code est incorrect.'; + $step = 'code'; + } elseif (\strlen($newPassword) < 8) { + $error = 'Le mot de passe doit contenir au moins 8 caracteres.'; + $step = 'code'; + } else { + $user = $userRepository->findOneBy(['email' => $sessionEmail]); + + if (null !== $user) { + $user->setPassword($passwordHasher->hashPassword($user, $newPassword)); + $em->flush(); + + // Email de notification du changement + $mailer->sendEmail( + $sessionEmail, + 'CRM Ecosplay - Mot de passe modifie', + $twig->render('emails/password_changed.html.twig'), + null, + null, + false, + ); + } + + // Nettoyer la session + $request->getSession()->remove('reset_step'); + $request->getSession()->remove('reset_code'); + $request->getSession()->remove('reset_email'); + $request->getSession()->remove('reset_expires'); + + $this->addFlash('success', 'Votre mot de passe a ete modifie avec succes.'); + + return $this->redirectToRoute('app_home'); + } + } + } + + return $this->render('security/forgot_password.html.twig', [ + 'step' => $step, + 'email' => $request->getSession()->get('reset_email', ''), + 'error' => $error, + 'success' => $success, + ]); + } +} diff --git a/src/Controller/HomeController.php b/src/Controller/HomeController.php new file mode 100644 index 0000000..d41527c --- /dev/null +++ b/src/Controller/HomeController.php @@ -0,0 +1,41 @@ +getUser()) { + return $this->redirectToRoute($this->resolveRedirectRoute()); + } + + return $this->render('home/index.html.twig', [ + 'breadcrumbs' => [ + ['name' => 'Accueil'], + ], + ]); + } + + private function resolveRedirectRoute(): string + { + if ($this->isGranted('ROLE_EMPLOYE')) { + return 'app_admin_dashboard'; + } + + if ($this->isGranted('ROLE_REVENDEUR')) { + return 'app_espace_prestataire_index'; + } + + if ($this->isGranted('ROLE_CUSTOMER')) { + return 'app_espace_client_index'; + } + + return 'app_home'; + } +} diff --git a/src/Controller/KeycloakController.php b/src/Controller/KeycloakController.php new file mode 100644 index 0000000..b3955e9 --- /dev/null +++ b/src/Controller/KeycloakController.php @@ -0,0 +1,25 @@ +getClient('keycloak')->redirect(['openid', 'email', 'profile'], []); + } + + #[Route('/connect/keycloak/check', name: 'connect_keycloak_check')] + public function check(): Response + { + // Ce controller ne sera jamais execute car le firewall intercepte la requete + return new Response('', Response::HTTP_OK); + } +} diff --git a/src/Controller/LegalController.php b/src/Controller/LegalController.php new file mode 100644 index 0000000..db7d64d --- /dev/null +++ b/src/Controller/LegalController.php @@ -0,0 +1,156 @@ +render('legal/mention_legal.html.twig', [ + 'breadcrumbs' => [ + ['name' => 'Accueil', 'url' => '/'], + ['name' => 'Mentions Legales'], + ], + ]); + } + + #[Route('/conditions-general-utilisation', name: 'cgu')] + public function cgu(): Response + { + return $this->render('legal/cgu.html.twig', [ + 'breadcrumbs' => [ + ['name' => 'Accueil', 'url' => '/'], + ['name' => 'CGU'], + ], + ]); + } + + #[Route('/conditions-general-de-vente', name: 'cgv')] + public function cgv(): Response + { + return $this->render('legal/cgv.html.twig', [ + 'breadcrumbs' => [ + ['name' => 'Accueil', 'url' => '/'], + ['name' => 'CGV'], + ], + ]); + } + + #[Route('/cookie', name: 'cookie')] + public function cookie(): Response + { + return $this->render('legal/cookie.html.twig', [ + 'breadcrumbs' => [ + ['name' => 'Accueil', 'url' => '/'], + ['name' => 'Politique de Cookies'], + ], + ]); + } + + #[Route('/hebergement', name: 'hebergement')] + public function hebergement(): Response + { + return $this->render('legal/hebergement.html.twig', [ + 'breadcrumbs' => [ + ['name' => 'Accueil', 'url' => '/'], + ['name' => 'Hebergement'], + ], + ]); + } + + #[Route('/conformite', name: 'conformite')] + public function conformite(): Response + { + return $this->render('legal/conformite.html.twig', [ + 'breadcrumbs' => [ + ['name' => 'Accueil', 'url' => '/'], + ['name' => 'Conformite'], + ], + ]); + } + + #[Route('/tarif', name: 'tarif')] + public function tarif(): Response + { + return $this->render('legal/tarif.html.twig', [ + 'breadcrumbs' => [ + ['name' => 'Accueil', 'url' => '/'], + ['name' => 'Tarifs'], + ], + ]); + } + + #[Route('/rgpd', name: 'rgpd', methods: ['GET'])] + public function rgpd(Request $request): Response + { + return $this->render('legal/rgpd.html.twig', [ + 'client_ip' => $request->getClientIp(), + 'breadcrumbs' => [ + ['name' => 'Accueil', 'url' => '/'], + ['name' => 'Politique RGPD'], + ], + ]); + } + + #[Route('/rgpd/acces', name: 'rgpd_access', methods: ['POST'])] + public function rgpdAccess(Request $request, RgpdService $rgpd): Response + { + $ip = trim($request->request->getString('ip')); + $email = trim($request->request->getString('email')); + + if ('' === $ip || '' === $email) { + $this->addFlash('error', 'Veuillez remplir tous les champs.'); + + return $this->redirect($this->generateUrl('app_legal_rgpd').'#exercer-droits'); + } + + try { + $result = $rgpd->handleAccessRequest($ip, $email); + + if ($result['found']) { + $this->addFlash('success', 'Vos donnees ont ete envoyees par email.'); + } else { + $this->addFlash('success', 'Aucune donnee trouvee pour cette adresse IP. Un email de confirmation a ete envoye.'); + } + } catch (\Throwable) { + $this->addFlash('error', 'Une erreur est survenue lors du traitement de votre demande. Veuillez reessayer ou nous contacter a contact@e-cosplay.fr.'); + } + + return $this->redirect($this->generateUrl('app_legal_rgpd').'#exercer-droits'); + } + + #[Route('/rgpd/suppression', name: 'rgpd_deletion', methods: ['POST'])] + public function rgpdDeletion(Request $request, RgpdService $rgpd): Response + { + $ip = trim($request->request->getString('ip')); + $email = trim($request->request->getString('email')); + + if ('' === $ip || '' === $email) { + $this->addFlash('error', 'Veuillez remplir tous les champs.'); + + return $this->redirect($this->generateUrl('app_legal_rgpd').'#exercer-droits'); + } + + try { + $result = $rgpd->handleDeletionRequest($ip, $email); + + if ($result['found']) { + $this->addFlash('success', 'Vos donnees ont ete supprimees. Une attestation a ete envoyee par email.'); + } else { + $this->addFlash('success', 'Aucune donnee trouvee pour cette adresse IP.'); + } + } catch (\Throwable) { + $this->addFlash('error', 'Une erreur est survenue lors du traitement de votre demande. Veuillez reessayer ou nous contacter a contact@e-cosplay.fr.'); + } + + return $this->redirect($this->generateUrl('app_legal_rgpd').'#exercer-droits'); + } +} diff --git a/src/Controller/SecurityController.php b/src/Controller/SecurityController.php new file mode 100644 index 0000000..aadda7e --- /dev/null +++ b/src/Controller/SecurityController.php @@ -0,0 +1,16 @@ +findOneBy(['tempPassword' => $token]); + + if (null === $user) { + return $this->render('security/set_password_expired.html.twig'); + } + + $error = null; + + if ('POST' === $request->getMethod()) { + $password = $request->request->getString('password'); + $passwordConfirm = $request->request->getString('password_confirm'); + + if (\strlen($password) < 8) { + $error = 'Le mot de passe doit contenir au moins 8 caracteres.'; + } elseif ($password !== $passwordConfirm) { + $error = 'Les mots de passe ne correspondent pas.'; + } else { + $user->setPassword($passwordHasher->hashPassword($user, $password)); + $user->clearTempPassword(); + $em->flush(); + + $this->addFlash('success', 'Votre mot de passe a ete defini. Vous pouvez maintenant vous connecter.'); + + return $this->redirectToRoute('app_home'); + } + } + + return $this->render('security/set_password.html.twig', [ + 'user' => $user, + 'error' => $error, + ]); + } +} diff --git a/src/Controller/SonarBadgeController.php b/src/Controller/SonarBadgeController.php new file mode 100644 index 0000000..4a677b3 --- /dev/null +++ b/src/Controller/SonarBadgeController.php @@ -0,0 +1,49 @@ +request('GET', $url); + $svg = $response->getContent(false); + + return new Response($svg, 200, [ + 'Content-Type' => 'image/svg+xml', + 'Cache-Control' => 'public, max-age=300', + ]); + } +} diff --git a/src/Controller/StatusPageController.php b/src/Controller/StatusPageController.php new file mode 100644 index 0000000..58d829e --- /dev/null +++ b/src/Controller/StatusPageController.php @@ -0,0 +1,89 @@ +findBy([], ['position' => 'ASC']); + + $servicesData = []; + $globalStatus = 'up'; + + foreach ($categories as $category) { + $categoryData = [ + 'category' => $category, + 'services' => [], + ]; + + foreach ($category->getServices() as $service) { + $history = $serviceRepository->getHistoryForDays($service, 30); + $dailyStatus = $serviceRepository->getDailyStatus($service, 30); + $uptime = $service->computeUptimeRatio($history, 30); + + // Messages actifs + $messages = $em->getRepository(ServiceMessage::class)->findBy( + ['service' => $service, 'isActive' => true], + ['createdAt' => 'DESC'], + ); + + // Logs recents (7 derniers jours) + $logs = $em->createQueryBuilder() + ->select('l') + ->from('App\Entity\ServiceLog', 'l') + ->where('l.service = :service') + ->andWhere('l.createdAt >= :since') + ->setParameter('service', $service) + ->setParameter('since', new \DateTimeImmutable('-7 days')) + ->orderBy('l.createdAt', 'DESC') + ->getQuery() + ->getResult(); + + $categoryData['services'][] = [ + 'service' => $service, + 'uptime' => $uptime, + 'dailyStatus' => $dailyStatus, + 'messages' => $messages, + 'logs' => $logs, + ]; + + // Global status + if ('down' === $service->getStatus()) { + $globalStatus = 'down'; + } elseif ('degraded' === $service->getStatus() && 'down' !== $globalStatus) { + $globalStatus = 'degraded'; + } elseif ('maintenance' === $service->getStatus() && 'up' === $globalStatus) { + $globalStatus = 'maintenance'; + } + } + + $servicesData[] = $categoryData; + } + + // Messages globaux actifs + $globalMessages = $em->getRepository(ServiceMessage::class)->findBy( + ['isActive' => true], + ['createdAt' => 'DESC'], + ); + + return $this->render('status/index.html.twig', [ + 'servicesData' => $servicesData, + 'globalStatus' => $globalStatus, + 'globalMessages' => $globalMessages, + 'breadcrumbs' => [ + ['name' => 'Accueil', 'url' => '/'], + ['name' => 'Status des services'], + ], + ]); + } +} diff --git a/src/Controller/WebhookDocuSealController.php b/src/Controller/WebhookDocuSealController.php new file mode 100644 index 0000000..15a24b3 --- /dev/null +++ b/src/Controller/WebhookDocuSealController.php @@ -0,0 +1,228 @@ +headers->get($secretHeader, ''); + if ('' !== $secret && !hash_equals($secret, $headerValue)) { + $body = json_decode($request->getContent(), true); + $bodySecret = $body['secret'] ?? $body['webhook_secret'] ?? ''; + if ('' === $bodySecret || !hash_equals($secret, $bodySecret)) { + return new JsonResponse(['error' => 'Unauthorized'], Response::HTTP_UNAUTHORIZED); + } + } + + $payload = json_decode($request->getContent(), true); + + if (null === $payload) { + return new JsonResponse(['error' => 'Invalid payload'], Response::HTTP_BAD_REQUEST); + } + + $eventType = $payload['event_type'] ?? ''; + $data = $payload['data'] ?? []; + $submitterId = $data['id'] ?? null; + $metadata = $data['metadata'] ?? []; + $docType = $metadata['doc_type'] ?? null; + + // Router par doc_type + // Aussi chercher par reference dans le metadata + if ('attestation' === $docType && null === $this->findAttestation($submitterId, $attestationRepository)) { + $reference = $metadata['reference'] ?? null; + if (null !== $reference) { + $attestation = $attestationRepository->findOneBy(['reference' => $reference]); + if (null !== $attestation) { + $attestation->setSubmitterId((int) $submitterId); + $em->flush(); + } + } + } + + if ('attestation' === $docType || null !== $this->findAttestation($submitterId, $attestationRepository)) { + return match ($eventType) { + 'form.viewed' => $this->handleAttestationViewed($submitterId, $attestationRepository, $em), + 'form.started' => $this->handleAttestationStarted($submitterId, $attestationRepository, $em), + 'form.completed' => $this->handleAttestationCompleted($data, $attestationRepository, $mailer, $em, $twig, $projectDir), + 'form.declined' => $this->handleAttestationDeclined($data, $attestationRepository, $em), + default => new JsonResponse(['status' => 'ignored', 'event' => $eventType]), + }; + } + + return new JsonResponse(['status' => 'ignored', 'doc_type' => $docType]); + } + + private function findAttestation(?int $submitterId, AttestationRepository $repository): ?Attestation + { + if (null === $submitterId) { + return null; + } + + return $repository->findOneBy(['submitterId' => $submitterId]); + } + + private function handleAttestationViewed( + ?int $submitterId, + AttestationRepository $attestationRepository, + EntityManagerInterface $em, + ): JsonResponse { + $attestation = $this->findAttestation($submitterId, $attestationRepository); + + if (null === $attestation) { + return new JsonResponse(['error' => 'Attestation not found'], Response::HTTP_NOT_FOUND); + } + + // Rien a changer pour le moment, juste un log + return new JsonResponse(['status' => 'ok', 'event' => 'viewed', 'reference' => $attestation->getReference()]); + } + + private function handleAttestationStarted( + ?int $submitterId, + AttestationRepository $attestationRepository, + EntityManagerInterface $em, + ): JsonResponse { + $attestation = $this->findAttestation($submitterId, $attestationRepository); + + if (null === $attestation) { + return new JsonResponse(['error' => 'Attestation not found'], Response::HTTP_NOT_FOUND); + } + + return new JsonResponse(['status' => 'ok', 'event' => 'started', 'reference' => $attestation->getReference()]); + } + + private function handleAttestationCompleted( + array $data, + AttestationRepository $attestationRepository, + MailerService $mailer, + EntityManagerInterface $em, + Environment $twig, + string $projectDir, + ): JsonResponse { + $submitterId = $data['id'] ?? null; + $attestation = $this->findAttestation($submitterId, $attestationRepository); + + if (null === $attestation) { + return new JsonResponse(['error' => 'Attestation not found'], Response::HTTP_NOT_FOUND); + } + + $attestation->markAsSigned(); + + // Telecharger le PDF signe et le certificat depuis les URLs du webhook + $this->downloadDocumentsFromWebhook($data, $attestation, $projectDir); + + // Envoyer l'attestation signee par email + $signedPdf = $attestation->getPdfFileSigned(); + $certificatePdf = $attestation->getPdfFileCertificate(); + + $attachments = []; + if (null !== $signedPdf && file_exists($signedPdf)) { + $attachments[] = ['path' => $signedPdf, 'name' => 'attestation-signee-'.$attestation->getReference().'.pdf']; + } + if (null !== $certificatePdf && file_exists($certificatePdf)) { + $attachments[] = ['path' => $certificatePdf, 'name' => 'certificat-'.$attestation->getReference().'.pdf']; + } + + $typeName = match ($attestation->getType()) { + 'access' => 'acces aux donnees', + 'deletion' => 'suppression des donnees', + 'no_data' => 'absence de donnees', + default => 'RGPD', + }; + + $mailer->sendEmail( + $attestation->getEmail(), + 'CRM Ecosplay - Attestation signee '.$typeName.' ('.$attestation->getReference().')', + $twig->render('emails/rgpd_attestation_signed.html.twig', [ + 'attestation' => $attestation, + 'typeName' => $typeName, + ]), + null, + null, + false, + $attachments, + ); + + $attestation->markAsSent(); + $em->flush(); + + return new JsonResponse(['status' => 'ok', 'event' => 'completed', 'reference' => $attestation->getReference()]); + } + + private function downloadDocumentsFromWebhook(array $data, Attestation $attestation, string $projectDir): void + { + $dir = $projectDir.'/var/rgpd/signed'; + if (!is_dir($dir)) { + mkdir($dir, 0775, true); + } + + $ref = $attestation->getReference(); + + // PDF signe + $documents = $data['documents'] ?? []; + $pdfUrl = $documents[0]['url'] ?? null; + if (null !== $pdfUrl) { + $content = @file_get_contents($pdfUrl); + if (false !== $content && str_starts_with($content, '%PDF')) { + $path = $dir.'/'.$ref.'-signed.pdf'; + file_put_contents($path, $content); + $attestation->setPdfFileSigned($path); + } + } + + // Certificat d'audit + $auditUrl = $data['audit_log_url'] ?? null; + if (null !== $auditUrl) { + $content = @file_get_contents($auditUrl); + if (false !== $content) { + $path = $dir.'/'.$ref.'-certificat.pdf'; + file_put_contents($path, $content); + $attestation->setPdfFileCertificate($path); + } + } + } + + private function handleAttestationDeclined( + array $data, + AttestationRepository $attestationRepository, + EntityManagerInterface $em, + ): JsonResponse { + $submitterId = $data['id'] ?? null; + $attestation = $this->findAttestation($submitterId, $attestationRepository); + + if (null === $attestation) { + return new JsonResponse(['error' => 'Attestation not found'], Response::HTTP_NOT_FOUND); + } + + $reason = $data['decline_reason'] ?? 'Aucune raison fournie'; + + if ('signed' !== $attestation->getStatus()) { + // On remet en pending pour pouvoir retenter + $em->flush(); + } + + return new JsonResponse(['status' => 'ok', 'event' => 'declined', 'reference' => $attestation->getReference()]); + } +} diff --git a/src/Entity/AnalyticsEvent.php b/src/Entity/AnalyticsEvent.php new file mode 100644 index 0000000..ed7aff1 --- /dev/null +++ b/src/Entity/AnalyticsEvent.php @@ -0,0 +1,110 @@ +createdAt = new \DateTimeImmutable(); + } + + public function getId(): ?int + { + return $this->id; + } + + public function getVisitor(): AnalyticsUniqId + { + return $this->visitor; + } + + public function setVisitor(AnalyticsUniqId $visitor): static + { + $this->visitor = $visitor; + + return $this; + } + + public function getEventName(): string + { + return $this->eventName; + } + + public function setEventName(string $eventName): static + { + $this->eventName = $eventName; + + return $this; + } + + public function getUrl(): string + { + return $this->url; + } + + public function setUrl(string $url): static + { + $this->url = $url; + + return $this; + } + + public function getTitle(): ?string + { + return $this->title; + } + + public function setTitle(?string $title): static + { + $this->title = $title; + + return $this; + } + + public function getReferrer(): ?string + { + return $this->referrer; + } + + public function setReferrer(?string $referrer): static + { + $this->referrer = $referrer; + + return $this; + } + + public function getCreatedAt(): \DateTimeImmutable + { + return $this->createdAt; + } +} diff --git a/src/Entity/AnalyticsUniqId.php b/src/Entity/AnalyticsUniqId.php new file mode 100644 index 0000000..1594453 --- /dev/null +++ b/src/Entity/AnalyticsUniqId.php @@ -0,0 +1,267 @@ + */ + #[ORM\OneToMany(targetEntity: AnalyticsEvent::class, mappedBy: 'visitor', cascade: ['remove'])] + private Collection $events; + + public function __construct() + { + $this->createdAt = new \DateTimeImmutable(); + $this->events = new ArrayCollection(); + } + + public function getId(): ?int + { + return $this->id; + } + + public function getUid(): string + { + return $this->uid; + } + + public function setUid(string $uid): static + { + $this->uid = $uid; + + return $this; + } + + public function getHash(): string + { + return $this->hash; + } + + public function setHash(string $hash): static + { + $this->hash = $hash; + + return $this; + } + + public function getUser(): ?User + { + return $this->user; + } + + public function setUser(?User $user): static + { + $this->user = $user; + + return $this; + } + + public function getIpHash(): string + { + return $this->ipHash; + } + + public function setIpHash(string $ipHash): static + { + $this->ipHash = $ipHash; + + return $this; + } + + public function getUserAgent(): string + { + return $this->userAgent; + } + + public function setUserAgent(string $userAgent): static + { + $this->userAgent = $userAgent; + + return $this; + } + + public function getScreenWidth(): ?int + { + return $this->screenWidth; + } + + public function setScreenWidth(?int $screenWidth): static + { + $this->screenWidth = $screenWidth; + + return $this; + } + + public function getScreenHeight(): ?int + { + return $this->screenHeight; + } + + public function setScreenHeight(?int $screenHeight): static + { + $this->screenHeight = $screenHeight; + + return $this; + } + + public function getLanguage(): ?string + { + return $this->language; + } + + public function setLanguage(?string $language): static + { + $this->language = $language; + + return $this; + } + + public function getDeviceType(): string + { + return $this->deviceType; + } + + public function setDeviceType(string $deviceType): static + { + $this->deviceType = $deviceType; + + return $this; + } + + public function getOs(): ?string + { + return $this->os; + } + + public function setOs(?string $os): static + { + $this->os = $os; + + return $this; + } + + public function getBrowser(): ?string + { + return $this->browser; + } + + public function setBrowser(?string $browser): static + { + $this->browser = $browser; + + return $this; + } + + public function getCreatedAt(): \DateTimeImmutable + { + return $this->createdAt; + } + + /** + * @return Collection + */ + public function getEvents(): Collection + { + return $this->events; + } + + public static function parseDeviceType(string $ua): string + { + $ua = strtolower($ua); + if (preg_match('/tablet|ipad|playbook|silk/i', $ua)) { + return 'tablet'; + } + if (preg_match('/mobile|iphone|ipod|android.*mobile|windows phone|blackberry/i', $ua)) { + return 'mobile'; + } + + return 'desktop'; + } + + public static function parseOs(string $ua): ?string + { + $patterns = [ + '/windows nt/i' => 'Windows', + '/macintosh|mac os x/i' => 'macOS', + '/iphone|ipad|ipod/i' => 'iOS', + '/android/i' => 'Android', + '/linux/i' => 'Linux', + '/cros/i' => 'ChromeOS', + ]; + + foreach ($patterns as $pattern => $name) { + if (preg_match($pattern, $ua)) { + return $name; + } + } + + return null; + } + + public static function parseBrowser(string $ua): ?string + { + $patterns = [ + '/edg(?:e|a)?\/[\d.]+/i' => 'Edge', + '/opr\/[\d.]+|opera/i' => 'Opera', + '/chrome\/[\d.]+(?!.*edg)/i' => 'Chrome', + '/safari\/[\d.]+(?!.*chrome)/i' => 'Safari', + '/firefox\/[\d.]+/i' => 'Firefox', + ]; + + foreach ($patterns as $pattern => $name) { + if (preg_match($pattern, $ua)) { + return $name; + } + } + + return null; + } +} diff --git a/src/Entity/Attestation.php b/src/Entity/Attestation.php new file mode 100644 index 0000000..6438ee8 --- /dev/null +++ b/src/Entity/Attestation.php @@ -0,0 +1,216 @@ + 'pending'])] + private string $status = 'pending'; + + #[ORM\Column] + private \DateTimeImmutable $createdAt; + + #[ORM\OneToOne(targetEntity: EmailTracking::class)] + #[ORM\JoinColumn(nullable: true, onDelete: 'SET NULL')] + private ?EmailTracking $emailTracking = null; + + public function __construct(string $type, string $ip, string $email, string $hmacSecret) + { + $this->type = $type; + $this->ip = $ip; + $this->email = $email; + $this->createdAt = new \DateTimeImmutable(); + $this->reference = $this->generateReference(); + $this->hmac = $this->generateHmac($hmacSecret); + } + + private function generateReference(): string + { + $prefix = match ($this->type) { + 'access' => 'ACC', + 'deletion' => 'DEL', + 'no_data' => 'ABS', + default => 'ATT', + }; + + $rev = strtoupper(bin2hex(random_bytes(4))); + + return sprintf('RGPD-%s-%s-%s', $prefix, $this->createdAt->format('dmY'), $rev); + } + + private function generateHmac(string $secret): string + { + $payload = implode('|', [ + $this->reference, + $this->type, + $this->ip, + $this->email, + $this->createdAt->format('Y-m-d\TH:i:s'), + ]); + + return hash_hmac('sha256', $payload, $secret); + } + + public function getId(): ?int + { + return $this->id; + } + + public function getReference(): string + { + return $this->reference; + } + + public function getType(): string + { + return $this->type; + } + + public function getIp(): string + { + return $this->ip; + } + + public function getEmail(): string + { + return $this->email; + } + + public function getHmac(): string + { + return $this->hmac; + } + + public function getCreatedAt(): \DateTimeImmutable + { + return $this->createdAt; + } + + public function getEmailTracking(): ?EmailTracking + { + return $this->emailTracking; + } + + public function setEmailTracking(?EmailTracking $emailTracking): static + { + $this->emailTracking = $emailTracking; + + return $this; + } + + public function getPdfFileUnsigned(): ?string + { + return $this->pdfFileUnsigned; + } + + public function setPdfFileUnsigned(?string $path): static + { + $this->pdfFileUnsigned = $path; + + return $this; + } + + public function getPdfFileSigned(): ?string + { + return $this->pdfFileSigned; + } + + public function setPdfFileSigned(?string $path): static + { + $this->pdfFileSigned = $path; + + return $this; + } + + public function getPdfFileCertificate(): ?string + { + return $this->pdfFileCertificate; + } + + public function setPdfFileCertificate(?string $path): static + { + $this->pdfFileCertificate = $path; + + return $this; + } + + public function getSubmitterId(): ?int + { + return $this->submitterId; + } + + public function setSubmitterId(?int $submitterId): static + { + $this->submitterId = $submitterId; + + return $this; + } + + public function getStatus(): string + { + return $this->status; + } + + public function markAsSigned(): static + { + $this->status = 'signed'; + + return $this; + } + + public function markAsSent(): static + { + $this->status = 'sent'; + + return $this; + } + + public function verify(string $secret): bool + { + $payload = implode('|', [ + $this->reference, + $this->type, + $this->ip, + $this->email, + $this->createdAt->format('Y-m-d\TH:i:s'), + ]); + + return hash_equals($this->hmac, hash_hmac('sha256', $payload, $secret)); + } +} diff --git a/src/Entity/Customer.php b/src/Entity/Customer.php new file mode 100644 index 0000000..802fb08 --- /dev/null +++ b/src/Entity/Customer.php @@ -0,0 +1,355 @@ +user = $user; + $this->createdAt = new \DateTimeImmutable(); + } + + public function getCodeComptable(): ?string + { + return $this->codeComptable; + } + + public function setCodeComptable(?string $codeComptable): static + { + $this->codeComptable = $codeComptable; + + return $this; + } + + public function generateCodeComptable(): string + { + $prefix = 'EC'; + $idPart = str_pad((string) ($this->id ?? 0), 4, '0', STR_PAD_LEFT); + $namePart = ''; + + if (null !== $this->raisonSociale && '' !== $this->raisonSociale) { + $clean = preg_replace('/[^A-Za-z]/', '', $this->raisonSociale); + $namePart = strtoupper(substr($clean, 0, 5)); + } elseif (null !== $this->lastName && '' !== $this->lastName) { + $clean = preg_replace('/[^A-Za-z]/', '', $this->lastName); + $namePart = strtoupper(substr($clean, 0, 5)); + } + + $namePart = str_pad($namePart, 5, 'X'); + + return $prefix.'_'.$idPart.'_'.$namePart; + } + + public function getId(): ?int + { + return $this->id; + } + + public function getUser(): User + { + return $this->user; + } + + public function getRaisonSociale(): ?string + { + return $this->raisonSociale; + } + + public function setRaisonSociale(?string $raisonSociale): static + { + $this->raisonSociale = $raisonSociale; + + return $this; + } + + public function getFirstName(): ?string + { + return $this->firstName; + } + + public function setFirstName(?string $firstName): static + { + $this->firstName = $firstName; + + return $this; + } + + public function getLastName(): ?string + { + return $this->lastName; + } + + public function setLastName(?string $lastName): static + { + $this->lastName = $lastName; + + return $this; + } + + public function getFullName(): string + { + if (null !== $this->raisonSociale && '' !== $this->raisonSociale) { + return $this->raisonSociale; + } + + return trim(($this->firstName ?? '').' '.($this->lastName ?? '')); + } + + public function getEmail(): ?string + { + return $this->email; + } + + public function setEmail(?string $email): static + { + $this->email = $email; + + return $this; + } + + public function getPhone(): ?string + { + return $this->phone; + } + + public function setPhone(?string $phone): static + { + $this->phone = $phone; + + return $this; + } + + public function getAddress(): ?string + { + return $this->address; + } + + public function setAddress(?string $address): static + { + $this->address = $address; + + return $this; + } + + public function getAddress2(): ?string + { + return $this->address2; + } + + public function setAddress2(?string $address2): static + { + $this->address2 = $address2; + + return $this; + } + + public function getZipCode(): ?string + { + return $this->zipCode; + } + + public function setZipCode(?string $zipCode): static + { + $this->zipCode = $zipCode; + + return $this; + } + + public function getCity(): ?string + { + return $this->city; + } + + public function setCity(?string $city): static + { + $this->city = $city; + + return $this; + } + + public function getSiret(): ?string + { + return $this->siret; + } + + public function setSiret(?string $siret): static + { + $this->siret = $siret; + + return $this; + } + + public function getRcs(): ?string + { + return $this->rcs; + } + + public function setRcs(?string $rcs): static + { + $this->rcs = $rcs; + + return $this; + } + + public function getNumTva(): ?string + { + return $this->numTva; + } + + public function setNumTva(?string $numTva): static + { + $this->numTva = $numTva; + + return $this; + } + + public function getStripeCustomerId(): ?string + { + return $this->stripeCustomerId; + } + + public function setStripeCustomerId(?string $stripeCustomerId): static + { + $this->stripeCustomerId = $stripeCustomerId; + + return $this; + } + + public function getTypeCompany(): ?string + { + return $this->typeCompany; + } + + public function setTypeCompany(?string $typeCompany): static + { + $this->typeCompany = $typeCompany; + + return $this; + } + + public function getState(): string + { + return $this->state; + } + + public function setState(string $state): static + { + $this->state = $state; + $this->updatedAt = new \DateTimeImmutable(); + + return $this; + } + + public function isActive(): bool + { + return self::STATE_ACTIVE === $this->state; + } + + public function getCreatedAt(): \DateTimeImmutable + { + return $this->createdAt; + } + + public function getUpdatedAt(): ?\DateTimeImmutable + { + return $this->updatedAt; + } +} diff --git a/src/Entity/EmailTracking.php b/src/Entity/EmailTracking.php new file mode 100644 index 0000000..f834ea0 --- /dev/null +++ b/src/Entity/EmailTracking.php @@ -0,0 +1,106 @@ + */ + #[ORM\Column(type: 'json', nullable: true)] + private ?array $attachments = null; + + /** @param list|null $attachments */ + public function __construct(string $messageId, string $recipient, string $subject, ?string $htmlBody = null, ?array $attachments = null) + { + $this->messageId = $messageId; + $this->recipient = $recipient; + $this->subject = $subject; + $this->htmlBody = $htmlBody; + $this->attachments = $attachments; + $this->state = 'sent'; + $this->sentAt = new \DateTimeImmutable(); + } + + public function getId(): ?int + { + return $this->id; + } + + public function getMessageId(): ?string + { + return $this->messageId; + } + + public function getRecipient(): ?string + { + return $this->recipient; + } + + public function getSubject(): ?string + { + return $this->subject; + } + + public function getState(): ?string + { + return $this->state; + } + + public function getSentAt(): ?\DateTimeImmutable + { + return $this->sentAt; + } + + public function getOpenedAt(): ?\DateTimeImmutable + { + return $this->openedAt; + } + + public function getHtmlBody(): ?string + { + return $this->htmlBody; + } + + /** @return list|null */ + public function getAttachments(): ?array + { + return $this->attachments; + } + + public function markAsOpened(): void + { + if ('sent' === $this->state) { + $this->state = 'opened'; + $this->openedAt = new \DateTimeImmutable(); + } + } +} diff --git a/src/Entity/MessengerLog.php b/src/Entity/MessengerLog.php new file mode 100644 index 0000000..a2481be --- /dev/null +++ b/src/Entity/MessengerLog.php @@ -0,0 +1,126 @@ +messageClass = $messageClass; + $this->messageBody = $messageBody; + $this->status = 'failed'; + $this->errorMessage = $errorMessage; + $this->stackTrace = $stackTrace; + $this->transportName = $transportName; + $this->retryCount = $retryCount; + $this->createdAt = new \DateTimeImmutable(); + $this->failedAt = new \DateTimeImmutable(); + } + + public function getId(): ?int + { + return $this->id; + } + + public function getMessageClass(): ?string + { + return $this->messageClass; + } + + public function getMessageBody(): ?string + { + return $this->messageBody; + } + + public function getStatus(): ?string + { + return $this->status; + } + + public function setStatus(string $status): static + { + $this->status = $status; + + return $this; + } + + public function getErrorMessage(): ?string + { + return $this->errorMessage; + } + + public function getStackTrace(): ?string + { + return $this->stackTrace; + } + + public function getTransportName(): ?string + { + return $this->transportName; + } + + public function getRetryCount(): ?int + { + return $this->retryCount; + } + + public function getCreatedAt(): ?\DateTimeImmutable + { + return $this->createdAt; + } + + public function getFailedAt(): ?\DateTimeImmutable + { + return $this->failedAt; + } + + public function markAsResolved(): void + { + $this->status = 'resolved'; + } +} diff --git a/src/Entity/Revendeur.php b/src/Entity/Revendeur.php new file mode 100644 index 0000000..489b9d6 --- /dev/null +++ b/src/Entity/Revendeur.php @@ -0,0 +1,277 @@ +user = $user; + $this->codeRevendeur = $codeRevendeur; + $this->createdAt = new \DateTimeImmutable(); + } + + public function getId(): ?int + { + return $this->id; + } + + public function getUser(): User + { + return $this->user; + } + + public function getCodeRevendeur(): string + { + return $this->codeRevendeur; + } + + public function getRaisonSociale(): ?string + { + return $this->raisonSociale; + } + + public function setRaisonSociale(?string $raisonSociale): static + { + $this->raisonSociale = $raisonSociale; + + return $this; + } + + public function getSiret(): ?string + { + return $this->siret; + } + + public function setSiret(?string $siret): static + { + $this->siret = $siret; + + return $this; + } + + public function getAddress(): ?string + { + return $this->address; + } + + public function setAddress(?string $address): static + { + $this->address = $address; + + return $this; + } + + public function getZipCode(): ?string + { + return $this->zipCode; + } + + public function setZipCode(?string $zipCode): static + { + $this->zipCode = $zipCode; + + return $this; + } + + public function getCity(): ?string + { + return $this->city; + } + + public function setCity(?string $city): static + { + $this->city = $city; + + return $this; + } + + public function getEmail(): ?string + { + return $this->email; + } + + public function setEmail(?string $email): static + { + $this->email = $email; + + return $this; + } + + public function getPhone(): ?string + { + return $this->phone; + } + + public function setPhone(?string $phone): static + { + $this->phone = $phone; + + return $this; + } + + public function getStripeConnectId(): ?string + { + return $this->stripeConnectId; + } + + public function setStripeConnectId(?string $stripeConnectId): static + { + $this->stripeConnectId = $stripeConnectId; + + return $this; + } + + public function isUseStripe(): bool + { + return $this->isUseStripe; + } + + public function setIsUseStripe(bool $isUseStripe): static + { + $this->isUseStripe = $isUseStripe; + + return $this; + } + + public function getContratResellerPdfUnsigned(): ?string + { + return $this->contratResellerPdfUnsigned; + } + + public function setContratResellerPdfUnsigned(?string $path): static + { + $this->contratResellerPdfUnsigned = $path; + + return $this; + } + + public function getContratResellerPdfSigned(): ?string + { + return $this->contratResellerPdfSigned; + } + + public function setContratResellerPdfSigned(?string $path): static + { + $this->contratResellerPdfSigned = $path; + + return $this; + } + + public function getContratResellerPdfAudit(): ?string + { + return $this->contratResellerPdfAudit; + } + + public function setContratResellerPdfAudit(?string $path): static + { + $this->contratResellerPdfAudit = $path; + + return $this; + } + + public function getContratSubmitterId(): ?int + { + return $this->contratSubmitterId; + } + + public function setContratSubmitterId(?int $contratSubmitterId): static + { + $this->contratSubmitterId = $contratSubmitterId; + + return $this; + } + + public function isActive(): bool + { + return $this->isActive; + } + + public function setIsActive(bool $isActive): static + { + $this->isActive = $isActive; + + return $this; + } + + public function getCreatedAt(): \DateTimeImmutable + { + return $this->createdAt; + } + + public function getUpdatedAt(): ?\DateTimeImmutable + { + return $this->updatedAt; + } + + public function setUpdatedAt(?\DateTimeImmutable $updatedAt): static + { + $this->updatedAt = $updatedAt; + + return $this; + } +} diff --git a/src/Entity/Service.php b/src/Entity/Service.php new file mode 100644 index 0000000..bf17284 --- /dev/null +++ b/src/Entity/Service.php @@ -0,0 +1,236 @@ + false])] + private bool $isExternal = false; + + #[ORM\Column(length: 50, nullable: true)] + private ?string $externalType = null; + + #[ORM\Column] + private int $position = 0; + + /** @var Collection */ + #[ORM\OneToMany(targetEntity: ServiceStatusHistory::class, mappedBy: 'service', cascade: ['persist', 'remove'], orphanRemoval: true)] + #[ORM\OrderBy(['createdAt' => 'DESC'])] + private Collection $statusHistory; + + #[ORM\Column] + private \DateTimeImmutable $createdAt; + + #[ORM\Column(nullable: true)] + private ?\DateTimeImmutable $updatedAt = null; + + public function __construct(string $name, string $slug, ServiceCategory $category) + { + $this->name = $name; + $this->slug = $slug; + $this->category = $category; + $this->statusHistory = new ArrayCollection(); + $this->createdAt = new \DateTimeImmutable(); + } + + public function getId(): ?int + { + return $this->id; + } + + public function getName(): string + { + return $this->name; + } + + public function setName(string $name): static + { + $this->name = $name; + + return $this; + } + + public function getSlug(): string + { + return $this->slug; + } + + public function getUrl(): ?string + { + return $this->url; + } + + public function setUrl(?string $url): static + { + $this->url = $url; + + return $this; + } + + public function getCategory(): ServiceCategory + { + return $this->category; + } + + public function getStatus(): string + { + return $this->status; + } + + public function setStatus(string $status, ?string $message = null): static + { + $oldStatus = $this->status; + $this->status = $status; + $this->message = $message; + $this->updatedAt = new \DateTimeImmutable(); + + if ($oldStatus !== $status) { + $this->addStatusHistory($status, $message); + } + + return $this; + } + + public function getMessage(): ?string + { + return $this->message; + } + + public function isExternal(): bool + { + return $this->isExternal; + } + + public function setIsExternal(bool $isExternal): static + { + $this->isExternal = $isExternal; + + return $this; + } + + public function getExternalType(): ?string + { + return $this->externalType; + } + + public function setExternalType(?string $externalType): static + { + $this->externalType = $externalType; + + return $this; + } + + public function getPosition(): int + { + return $this->position; + } + + public function setPosition(int $position): static + { + $this->position = $position; + + return $this; + } + + /** @return Collection */ + public function getStatusHistory(): Collection + { + return $this->statusHistory; + } + + public function getCreatedAt(): \DateTimeImmutable + { + return $this->createdAt; + } + + public function getUpdatedAt(): ?\DateTimeImmutable + { + return $this->updatedAt; + } + + private function addStatusHistory(string $status, ?string $message): void + { + $history = new ServiceStatusHistory($this, $status, $message); + $this->statusHistory->add($history); + } + + /** + * Calcule le ratio d'uptime sur les N derniers jours. + * + * @param list $history + */ + public function computeUptimeRatio(array $history, int $days = 30): float + { + if ([] === $history) { + return 'up' === $this->status ? 100.0 : 0.0; + } + + $now = new \DateTimeImmutable(); + $start = $now->modify('-'.$days.' days'); + $totalSeconds = $days * 86400; + $downSeconds = 0; + $lastTimestamp = $now; + $downStatuses = [self::STATUS_DOWN, self::STATUS_MAINTENANCE]; + + foreach ($history as $entry) { + $entryDate = $entry->getCreatedAt(); + if ($entryDate < $start) { + $entryDate = $start; + } + + if (\in_array($entry->getStatus(), $downStatuses, true)) { + $downSeconds += $lastTimestamp->getTimestamp() - $entryDate->getTimestamp(); + } + + $lastTimestamp = $entryDate; + } + + $upSeconds = $totalSeconds - $downSeconds; + + return round(max(0, min(100, ($upSeconds / $totalSeconds) * 100)), 2); + } +} diff --git a/src/Entity/ServiceCategory.php b/src/Entity/ServiceCategory.php new file mode 100644 index 0000000..b21128f --- /dev/null +++ b/src/Entity/ServiceCategory.php @@ -0,0 +1,87 @@ + */ + #[ORM\OneToMany(targetEntity: Service::class, mappedBy: 'category', cascade: ['persist', 'remove'], orphanRemoval: true)] + #[ORM\OrderBy(['position' => 'ASC'])] + private Collection $services; + + #[ORM\Column] + private \DateTimeImmutable $createdAt; + + public function __construct(string $name, string $slug) + { + $this->name = $name; + $this->slug = $slug; + $this->services = new ArrayCollection(); + $this->createdAt = new \DateTimeImmutable(); + } + + public function getId(): ?int + { + return $this->id; + } + + public function getName(): string + { + return $this->name; + } + + public function setName(string $name): static + { + $this->name = $name; + + return $this; + } + + public function getSlug(): string + { + return $this->slug; + } + + public function getPosition(): int + { + return $this->position; + } + + public function setPosition(int $position): static + { + $this->position = $position; + + return $this; + } + + /** @return Collection */ + public function getServices(): Collection + { + return $this->services; + } + + public function getCreatedAt(): \DateTimeImmutable + { + return $this->createdAt; + } +} diff --git a/src/Entity/ServiceLog.php b/src/Entity/ServiceLog.php new file mode 100644 index 0000000..3a06575 --- /dev/null +++ b/src/Entity/ServiceLog.php @@ -0,0 +1,79 @@ +service = $service; + $this->fromStatus = $fromStatus; + $this->toStatus = $toStatus; + $this->source = $source; + $this->changedBy = $changedBy; + $this->createdAt = new \DateTimeImmutable(); + } + + public function getId(): ?int + { + return $this->id; + } + + public function getService(): Service + { + return $this->service; + } + + public function getFromStatus(): string + { + return $this->fromStatus; + } + + public function getToStatus(): string + { + return $this->toStatus; + } + + public function getSource(): string + { + return $this->source; + } + + public function getChangedBy(): ?User + { + return $this->changedBy; + } + + public function getCreatedAt(): \DateTimeImmutable + { + return $this->createdAt; + } +} diff --git a/src/Entity/ServiceMessage.php b/src/Entity/ServiceMessage.php new file mode 100644 index 0000000..0c20618 --- /dev/null +++ b/src/Entity/ServiceMessage.php @@ -0,0 +1,103 @@ +service = $service; + $this->title = $title; + $this->content = $content; + $this->severity = $severity; + $this->author = $author; + $this->createdAt = new \DateTimeImmutable(); + } + + public function getId(): ?int + { + return $this->id; + } + + public function getService(): Service + { + return $this->service; + } + + public function getTitle(): string + { + return $this->title; + } + + public function getContent(): string + { + return $this->content; + } + + public function getSeverity(): string + { + return $this->severity; + } + + public function isActive(): bool + { + return $this->isActive; + } + + public function getAuthor(): ?User + { + return $this->author; + } + + public function getCreatedAt(): \DateTimeImmutable + { + return $this->createdAt; + } + + public function getResolvedAt(): ?\DateTimeImmutable + { + return $this->resolvedAt; + } + + public function resolve(): static + { + $this->isActive = false; + $this->resolvedAt = new \DateTimeImmutable(); + + return $this; + } +} diff --git a/src/Entity/ServiceStatusHistory.php b/src/Entity/ServiceStatusHistory.php new file mode 100644 index 0000000..7aad6e9 --- /dev/null +++ b/src/Entity/ServiceStatusHistory.php @@ -0,0 +1,60 @@ +service = $service; + $this->status = $status; + $this->message = $message; + $this->createdAt = new \DateTimeImmutable(); + } + + public function getId(): ?int + { + return $this->id; + } + + public function getService(): Service + { + return $this->service; + } + + public function getStatus(): string + { + return $this->status; + } + + public function getMessage(): ?string + { + return $this->message; + } + + public function getCreatedAt(): \DateTimeImmutable + { + return $this->createdAt; + } +} diff --git a/src/Entity/User.php b/src/Entity/User.php new file mode 100644 index 0000000..b7c979d --- /dev/null +++ b/src/Entity/User.php @@ -0,0 +1,374 @@ + */ + #[ORM\Column] + private array $roles = []; + + #[ORM\Column] + private ?string $password = null; + + #[ORM\Column(length: 255, nullable: true)] + private ?string $avatar = null; + + #[Vich\UploadableField(mapping: 'user_avatar', fileNameProperty: 'avatar')] + #[Assert\Image(maxSize: '2M', mimeTypes: ['image/jpeg', 'image/png', 'image/webp'])] + private ?File $avatarFile = null; + + #[ORM\Column(nullable: true)] + private ?\DateTimeImmutable $updatedAt = null; + + #[ORM\Column(length: 255, nullable: true, unique: true)] + private ?string $keycloakId = null; + + #[ORM\Column(length: 255, nullable: true)] + private ?string $tempPassword = null; + + #[ORM\Column(nullable: true)] + private ?string $emailAuthCode = null; + + #[ORM\Column(options: ['default' => false])] + private bool $isEmailAuthEnabled = false; + + #[ORM\Column(length: 255, nullable: true)] + private ?string $googleAuthenticatorSecret = null; + + #[ORM\Column(options: ['default' => false])] + private bool $isGoogleAuthEnabled = false; + + /** @var list */ + #[ORM\Column(type: 'json', nullable: true)] + private ?array $backupCodes = null; + +#[ORM\Column] + private \DateTimeImmutable $createdAt; + + public function __construct() + { + $this->createdAt = new \DateTimeImmutable(); + } + + public function getId(): ?int + { + return $this->id; + } + + public function getEmail(): ?string + { + return $this->email; + } + + public function setEmail(string $email): static + { + $this->email = $email; + + return $this; + } + + public function getFirstName(): ?string + { + return $this->firstName; + } + + public function setFirstName(string $firstName): static + { + $this->firstName = $firstName; + + return $this; + } + + public function getLastName(): ?string + { + return $this->lastName; + } + + public function setLastName(string $lastName): static + { + $this->lastName = $lastName; + + return $this; + } + + public function getFullName(): string + { + return $this->firstName.' '.$this->lastName; + } + + public function getUserIdentifier(): string + { + return (string) $this->email; + } + + /** @return list */ + public function getRoles(): array + { + $roles = $this->roles; + $roles[] = 'ROLE_USER'; + + return array_unique($roles); + } + + /** @param list $roles */ + public function setRoles(array $roles): static + { + $this->roles = $roles; + + return $this; + } + + public function getPassword(): ?string + { + return $this->password; + } + + public function setPassword(string $password): static + { + $this->password = $password; + + return $this; + } + + public function eraseCredentials(): void + { + } + + /** @return array */ + public function __serialize(): array + { + return [ + 'id' => $this->id, + 'email' => $this->email, + 'password' => $this->password, + 'roles' => $this->roles, + 'firstName' => $this->firstName, + 'lastName' => $this->lastName, + ]; + } + + /** @param array $data */ + public function __unserialize(array $data): void + { + $this->id = $data['id']; + $this->email = $data['email']; + $this->password = $data['password']; + $this->roles = $data['roles']; + $this->firstName = $data['firstName']; + $this->lastName = $data['lastName']; + } + + public function getTempPassword(): ?string + { + return $this->tempPassword; + } + + public function setTempPassword(?string $tempPassword): static + { + $this->tempPassword = $tempPassword; + + return $this; + } + + public function hasTempPassword(): bool + { + return null !== $this->tempPassword; + } + + public function clearTempPassword(): static + { + $this->tempPassword = null; + + return $this; + } + + public function getAvatar(): ?string + { + return $this->avatar; + } + + public function setAvatar(?string $avatar): static + { + $this->avatar = $avatar; + + return $this; + } + + public function getAvatarFile(): ?File + { + return $this->avatarFile; + } + + public function setAvatarFile(?File $avatarFile): static + { + $this->avatarFile = $avatarFile; + + if (null !== $avatarFile) { + $this->updatedAt = new \DateTimeImmutable(); + } + + return $this; + } + + public function getUpdatedAt(): ?\DateTimeImmutable + { + return $this->updatedAt; + } + + // --- Email 2FA --- + + public function isEmailAuthEnabled(): bool + { + return $this->isEmailAuthEnabled && null === $this->keycloakId; + } + + public function setIsEmailAuthEnabled(bool $enabled): static + { + $this->isEmailAuthEnabled = $enabled; + + return $this; + } + + public function getEmailAuthRecipient(): string + { + return $this->email; + } + + public function getEmailAuthCode(): ?string + { + return $this->emailAuthCode; + } + + public function setEmailAuthCode(string $authCode): void + { + $this->emailAuthCode = $authCode; + } + + // --- Google Authenticator 2FA --- + + public function isGoogleAuthenticatorEnabled(): bool + { + return $this->isGoogleAuthEnabled && null !== $this->googleAuthenticatorSecret && null === $this->keycloakId; + } + + public function setIsGoogleAuthEnabled(bool $enabled): static + { + $this->isGoogleAuthEnabled = $enabled; + + return $this; + } + + public function getGoogleAuthenticatorUsername(): string + { + return $this->email; + } + + public function getGoogleAuthenticatorSecret(): ?string + { + return $this->googleAuthenticatorSecret; + } + + public function setGoogleAuthenticatorSecret(?string $secret): static + { + $this->googleAuthenticatorSecret = $secret; + + return $this; + } + + // --- Backup Codes --- + + public function isBackupCode(string $code): bool + { + return null !== $this->backupCodes && \in_array($code, $this->backupCodes, true); + } + + public function invalidateBackupCode(string $code): void + { + if (null === $this->backupCodes) { + return; + } + + $key = array_search($code, $this->backupCodes, true); + if (false !== $key) { + unset($this->backupCodes[$key]); + $this->backupCodes = array_values($this->backupCodes); + } + } + + /** @return list */ + public function getBackupCodes(): array + { + return $this->backupCodes ?? []; + } + + /** @param list $codes */ + public function setBackupCodes(array $codes): static + { + $this->backupCodes = $codes; + + return $this; + } + + public function generateBackupCodes(int $count = 8): static + { + $codes = []; + for ($i = 0; $i < $count; ++$i) { + $codes[] = bin2hex(random_bytes(4)); + } + $this->backupCodes = $codes; + + return $this; + } + + // --- Keycloak --- + + public function getKeycloakId(): ?string + { + return $this->keycloakId; + } + + public function setKeycloakId(?string $keycloakId): static + { + $this->keycloakId = $keycloakId; + + return $this; + } + +public function getCreatedAt(): \DateTimeImmutable + { + return $this->createdAt; + } +} diff --git a/src/EventListener/ExceptionListener.php b/src/EventListener/ExceptionListener.php new file mode 100644 index 0000000..53aec05 --- /dev/null +++ b/src/EventListener/ExceptionListener.php @@ -0,0 +1,51 @@ +kernelEnvironment) { + return; + } + + $exception = $event->getThrowable(); + $statusCode = $exception instanceof HttpExceptionInterface ? $exception->getStatusCode() : 500; + + if (404 === $statusCode) { + $template = 'error/404.html.twig'; + } else { + $template = 'error/500.html.twig'; + } + + $response = new Response( + $this->twig->render($template, [ + 'status_code' => $statusCode, + 'error_message' => $exception->getMessage(), + ]), + $statusCode, + ); + + if ($exception instanceof HttpExceptionInterface) { + $response->headers->add($exception->getHeaders()); + } + + $event->setResponse($response); + } +} diff --git a/src/EventSubscriber/CsrfProtectionSubscriber.php b/src/EventSubscriber/CsrfProtectionSubscriber.php new file mode 100644 index 0000000..1f09dc3 --- /dev/null +++ b/src/EventSubscriber/CsrfProtectionSubscriber.php @@ -0,0 +1,98 @@ + ['onKernelRequest', 5], + KernelEvents::RESPONSE => ['onKernelResponse', -10], + ]; + } + + public function onKernelRequest(RequestEvent $event): void + { + if (!$event->isMainRequest()) { + return; + } + + $request = $event->getRequest(); + + if (!$this->shouldVerifyCsrf($request)) { + return; + } + + $token = $request->request->getString(self::TOKEN_FIELD); + if ('' !== $token && !$this->csrfTokenManager->isTokenValid(new CsrfToken(self::TOKEN_ID, $token))) { + $event->setResponse(new Response('CSRF token invalid.', 403)); + } + } + + private function shouldVerifyCsrf(\Symfony\Component\HttpFoundation\Request $request): bool + { + if (!$request->isMethod('POST')) { + return false; + } + + if (\in_array($request->attributes->getString('_route'), self::EXCLUDED_ROUTES, true)) { + return false; + } + + return !str_contains($request->headers->get('Content-Type', ''), 'application/json'); + } + + public function onKernelResponse(ResponseEvent $event): void + { + if (!$event->isMainRequest()) { + return; + } + + $response = $event->getResponse(); + $contentType = $response->headers->get('Content-Type', ''); + + if (!str_contains($contentType, 'text/html')) { + return; + } + + $content = $response->getContent(); + if (!$content || !str_contains($content, 'method="post"')) { + return; + } + + $token = $this->csrfTokenManager->getToken(self::TOKEN_ID)->getValue(); + $hiddenField = ''; + + $content = str_replace('method="post">', 'method="post">'.$hiddenField, $content); + $response->setContent($content); + } +} diff --git a/src/EventSubscriber/MessengerFailureSubscriber.php b/src/EventSubscriber/MessengerFailureSubscriber.php new file mode 100644 index 0000000..3f81515 --- /dev/null +++ b/src/EventSubscriber/MessengerFailureSubscriber.php @@ -0,0 +1,80 @@ + 'onMessageFailed', + ]; + } + + public function onMessageFailed(WorkerMessageFailedEvent $event): void + { + $envelope = $event->getEnvelope(); + $message = $envelope->getMessage(); + $throwable = $event->getThrowable(); + + $retryCount = 0; + $redeliveryStamp = $envelope->last(RedeliveryStamp::class); + if ($redeliveryStamp instanceof RedeliveryStamp) { + $retryCount = $redeliveryStamp->getRetryCount(); + } + + $messageBody = null; + try { + $messageBody = serialize($message); + } catch (\Throwable) { + $messageBody = get_class($message).' (not serializable)'; + } + + $log = new MessengerLog( + messageClass: get_class($message), + messageBody: $messageBody, + errorMessage: $throwable->getMessage(), + stackTrace: $throwable->getTraceAsString(), + transportName: $event->getReceiverName(), + retryCount: $retryCount, + ); + + $this->em->persist($log); + $this->em->flush(); + + try { + $email = (new Email()) + ->from($this->adminEmail) + ->to($this->adminEmail) + ->subject('Alerte Messenger : Echec de traitement') + ->priority(Email::PRIORITY_HIGH) + ->text( + "Un message Messenger a echoue.\n\n". + 'Message: '.get_class($message)."\n". + 'Transport: '.$event->getReceiverName()."\n". + 'Retry: '.$retryCount."\n". + 'Erreur: '.$throwable->getMessage()."\n\n". + "Stack trace:\n".$throwable->getTraceAsString() + ); + + $this->mailer->send($email); + } catch (\Throwable $e) { + error_log('Failed to send messenger alert email: '.$e->getMessage()); + } + } +} diff --git a/src/Message/AnalyticsMessage.php b/src/Message/AnalyticsMessage.php new file mode 100644 index 0000000..821f7d1 --- /dev/null +++ b/src/Message/AnalyticsMessage.php @@ -0,0 +1,16 @@ + $payload + */ + public function __construct( + public readonly string $uid, + public readonly string $action, + public readonly array $payload = [], + ) { + } +} diff --git a/src/MessageHandler/AnalyticsMessageHandler.php b/src/MessageHandler/AnalyticsMessageHandler.php new file mode 100644 index 0000000..29ae2db --- /dev/null +++ b/src/MessageHandler/AnalyticsMessageHandler.php @@ -0,0 +1,52 @@ +em->getRepository(AnalyticsUniqId::class)->findOneBy(['uid' => $message->uid]); + if (!$visitor) { + return; + } + + if ('set_user' === $message->action) { + $userId = $message->payload['userId'] ?? null; + if ($userId) { + $user = $this->em->getRepository(User::class)->find($userId); + if ($user) { + $visitor->setUser($user); + $this->em->flush(); + } + } + + return; + } + + if ('page_view' === $message->action) { + $event = new AnalyticsEvent(); + $event->setVisitor($visitor); + $event->setEventName('page_view'); + $event->setUrl($message->payload['url'] ?? '/'); + $event->setTitle($message->payload['title'] ?? null); + $event->setReferrer($message->payload['referrer'] ?? null); + + $this->em->persist($event); + $this->em->flush(); + } + } +} diff --git a/src/Repository/AttestationRepository.php b/src/Repository/AttestationRepository.php new file mode 100644 index 0000000..924bb68 --- /dev/null +++ b/src/Repository/AttestationRepository.php @@ -0,0 +1,18 @@ + + */ +class AttestationRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, Attestation::class); + } +} diff --git a/src/Repository/CustomerRepository.php b/src/Repository/CustomerRepository.php new file mode 100644 index 0000000..28ab936 --- /dev/null +++ b/src/Repository/CustomerRepository.php @@ -0,0 +1,32 @@ + + */ +class CustomerRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, Customer::class); + } + + public function generateUniqueCodeComptable(Customer $customer): string + { + $base = $customer->generateCodeComptable(); + $code = $base; + $suffix = 1; + + while (null !== $this->findOneBy(['codeComptable' => $code])) { + $code = $base.'_'.$suffix; + ++$suffix; + } + + return $code; + } +} diff --git a/src/Repository/EmailTrackingRepository.php b/src/Repository/EmailTrackingRepository.php new file mode 100644 index 0000000..89b39a1 --- /dev/null +++ b/src/Repository/EmailTrackingRepository.php @@ -0,0 +1,18 @@ + + */ +class EmailTrackingRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, EmailTracking::class); + } +} diff --git a/src/Repository/RevendeurRepository.php b/src/Repository/RevendeurRepository.php new file mode 100644 index 0000000..8f68b0f --- /dev/null +++ b/src/Repository/RevendeurRepository.php @@ -0,0 +1,27 @@ + + */ +class RevendeurRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, Revendeur::class); + } + + public function generateUniqueCode(): string + { + do { + $code = 'EC-'.str_pad((string) random_int(0, 9999), 4, '0', STR_PAD_LEFT); + } while (null !== $this->findOneBy(['codeRevendeur' => $code])); + + return $code; + } +} diff --git a/src/Repository/ServiceCategoryRepository.php b/src/Repository/ServiceCategoryRepository.php new file mode 100644 index 0000000..6bc3e72 --- /dev/null +++ b/src/Repository/ServiceCategoryRepository.php @@ -0,0 +1,18 @@ + + */ +class ServiceCategoryRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, ServiceCategory::class); + } +} diff --git a/src/Repository/ServiceRepository.php b/src/Repository/ServiceRepository.php new file mode 100644 index 0000000..f2463bd --- /dev/null +++ b/src/Repository/ServiceRepository.php @@ -0,0 +1,70 @@ + + */ +class ServiceRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, Service::class); + } + + /** + * @return list + */ + public function getHistoryForDays(Service $service, int $days = 30): array + { + $since = new \DateTimeImmutable('-'.$days.' days'); + + return $this->getEntityManager() + ->getRepository(ServiceStatusHistory::class) + ->createQueryBuilder('h') + ->where('h.service = :service') + ->andWhere('h.createdAt >= :since') + ->setParameter('service', $service) + ->setParameter('since', $since) + ->orderBy('h.createdAt', 'DESC') + ->getQuery() + ->getResult(); + } + + /** + * Retourne les données pour le graphique uptime 30 jours. + * + * @return list + */ + public function getDailyStatus(Service $service, int $days = 30): array + { + $history = $this->getHistoryForDays($service, $days); + $result = []; + $now = new \DateTimeImmutable(); + + for ($i = $days - 1; $i >= 0; --$i) { + $date = $now->modify('-'.$i.' days'); + $dateStr = $date->format('Y-m-d'); + $dayStatus = 'up'; + + foreach ($history as $entry) { + if ($entry->getCreatedAt()->format('Y-m-d') === $dateStr) { + $dayStatus = $entry->getStatus(); + break; + } + } + + $result[] = [ + 'date' => $dateStr, + 'status' => $dayStatus, + ]; + } + + return $result; + } +} diff --git a/src/Repository/UserRepository.php b/src/Repository/UserRepository.php new file mode 100644 index 0000000..f9e9ad0 --- /dev/null +++ b/src/Repository/UserRepository.php @@ -0,0 +1,32 @@ + + */ +class UserRepository extends ServiceEntityRepository implements PasswordUpgraderInterface +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, User::class); + } + + public function upgradePassword(PasswordAuthenticatedUserInterface $user, string $newHashedPassword): void + { + if (!$user instanceof User) { + throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', $user::class)); + } + + $user->setPassword($newHashedPassword); + $this->getEntityManager()->persist($user); + $this->getEntityManager()->flush(); + } +} diff --git a/src/Security/KeycloakAuthenticator.php b/src/Security/KeycloakAuthenticator.php new file mode 100644 index 0000000..087d6eb --- /dev/null +++ b/src/Security/KeycloakAuthenticator.php @@ -0,0 +1,108 @@ +attributes->get('_route'); + } + + public function authenticate(Request $request): Passport + { + $client = $this->clientRegistry->getClient('keycloak'); + $accessToken = $this->fetchAccessToken($client); + + return new SelfValidatingPassport( + new UserBadge($accessToken->getToken(), function () use ($accessToken, $client) { + $keycloakUser = $client->fetchUserFromToken($accessToken); + $keycloakData = $keycloakUser->toArray(); + + $keycloakId = $keycloakData['sub']; + $email = $keycloakData['email'] ?? ''; + $firstName = $keycloakData['given_name'] ?? ''; + $lastName = $keycloakData['family_name'] ?? ''; + $groups = $keycloakData['groups'] ?? []; + + // Chercher l'utilisateur par keycloakId ou email + $user = $this->userRepository->findOneBy(['keycloakId' => $keycloakId]) + ?? $this->userRepository->findOneBy(['email' => $email]); + + if (null === $user) { + $user = new User(); + $user->setEmail($email); + $user->setPassword(''); + } + + $user->setKeycloakId($keycloakId); + $user->setFirstName($firstName); + $user->setLastName($lastName); + + // Determiner le role selon les groupes Keycloak + $roles = $this->resolveRoles($groups); + $user->setRoles($roles); + + $this->em->persist($user); + $this->em->flush(); + + return $user; + }), + ); + } + + public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response + { + return new RedirectResponse($this->router->generate('app_home')); + } + + public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response + { + $request->getSession()->getFlashBag()->add('error', 'Echec de la connexion E-Cosplay. Veuillez reessayer.'); + + return new RedirectResponse($this->router->generate('app_home')); + } + + public function start(Request $request, ?AuthenticationException $authException = null): Response + { + return new RedirectResponse($this->router->generate('app_home')); + } + + /** + * @param list $groups + * + * @return list + */ + private function resolveRoles(array $groups): array + { + if (\in_array('super_admin_asso', $groups, true)) { + return ['ROLE_ROOT']; + } + + return ['ROLE_EMPLOYE']; + } +} diff --git a/src/Security/LoginSuccessHandler.php b/src/Security/LoginSuccessHandler.php new file mode 100644 index 0000000..4916875 --- /dev/null +++ b/src/Security/LoginSuccessHandler.php @@ -0,0 +1,36 @@ +authorizationChecker->isGranted('ROLE_EMPLOYE')) { + return new RedirectResponse($this->router->generate('app_admin_dashboard')); + } + + if ($this->authorizationChecker->isGranted('ROLE_REVENDEUR')) { + return new RedirectResponse($this->router->generate('app_espace_prestataire_index')); + } + + if ($this->authorizationChecker->isGranted('ROLE_CUSTOMER')) { + return new RedirectResponse($this->router->generate('app_espace_client_index')); + } + + return new RedirectResponse($this->router->generate('app_home')); + } +} diff --git a/src/Security/TwoFactorCodeMailer.php b/src/Security/TwoFactorCodeMailer.php new file mode 100644 index 0000000..a27abda --- /dev/null +++ b/src/Security/TwoFactorCodeMailer.php @@ -0,0 +1,35 @@ +getEmailAuthCode(); + + $html = $this->twig->render('emails/2fa_code.html.twig', [ + 'code' => $code, + ]); + + $email = (new Email()) + ->from('CRM Ecosplay ') + ->to($user->getEmailAuthRecipient()) + ->subject('CRM Ecosplay - Code de verification') + ->html($html); + + $this->mailer->send($email); + } +} diff --git a/src/Service/AnalyticsCryptoService.php b/src/Service/AnalyticsCryptoService.php new file mode 100644 index 0000000..1c468f6 --- /dev/null +++ b/src/Service/AnalyticsCryptoService.php @@ -0,0 +1,72 @@ +key = substr(hash('sha256', $this->analyticsSecret, true), 0, 32); + } + + /** @param array $data */ + public function encrypt(array $data): string + { + $json = json_encode($data, \JSON_THROW_ON_ERROR); + $iv = random_bytes(12); + $encrypted = openssl_encrypt($json, 'aes-256-gcm', $this->key, \OPENSSL_RAW_DATA, $iv, $tag, '', 16); + + // Format compatible with Web Crypto API: iv + ciphertext + tag + return base64_encode($iv.$encrypted.$tag); + } + + /** @return array|null */ + public function decrypt(string $payload): ?array + { + $raw = base64_decode($payload, true); + if (false === $raw || \strlen($raw) < 28) { + return null; + } + + $iv = substr($raw, 0, 12); + + // Try JS format first: iv (12) + ciphertext_with_tag (tag is last 16 bytes) + $json = $this->tryDecryptJsFormat($iv, substr($raw, 12)); + + // Fallback: PHP format iv (12) + tag (16) + ciphertext + $json ??= openssl_decrypt(substr($raw, 28), 'aes-256-gcm', $this->key, \OPENSSL_RAW_DATA, $iv, substr($raw, 12, 16)) ?: null; + + return $json ? json_decode($json, true) : null; + } + + private function tryDecryptJsFormat(string $iv, string $ciphertextWithTag): ?string + { + if (\strlen($ciphertextWithTag) < 16) { + return null; + } + + $json = openssl_decrypt(substr($ciphertextWithTag, 0, -16), 'aes-256-gcm', $this->key, \OPENSSL_RAW_DATA, $iv, substr($ciphertextWithTag, -16)); + + return false !== $json ? $json : null; + } + + public function generateVisitorHash(string $uid): string + { + return hash_hmac('sha256', $uid, $this->key); + } + + public function verifyVisitorHash(string $uid, string $hash): bool + { + return hash_equals($this->generateVisitorHash($uid), $hash); + } + + public function getKeyForJs(): string + { + return base64_encode($this->key); + } +} diff --git a/src/Service/DocuSealService.php b/src/Service/DocuSealService.php new file mode 100644 index 0000000..f7cd13d --- /dev/null +++ b/src/Service/DocuSealService.php @@ -0,0 +1,146 @@ +api = new Api($apiKey, rtrim($baseUrl, '/').'/api'); + } + + /** + * Envoie l'attestation a DocuSeal pour auto-signature en un seul appel. + */ + public function signAttestation(Attestation $attestation): bool + { + $pdfPath = $attestation->getPdfFileUnsigned(); + + if (null === $pdfPath || !file_exists($pdfPath)) { + return false; + } + + try { + $pdfBase64 = base64_encode(file_get_contents($pdfPath)); + + $result = $this->api->createSubmissionFromPdf([ + 'name' => 'Attestation RGPD - '.$attestation->getReference(), + 'send_email' => false, + 'flatten' => true, + 'documents' => [ + [ + 'name' => 'attestation-'.$attestation->getReference().'.pdf', + 'file' => 'data:application/pdf;base64,'.$pdfBase64, + ], + ], + 'submitters' => [ + [ + 'email' => 'contact@e-cosplay.fr', + 'name' => 'Association E-Cosplay', + 'role' => 'First Party', + 'completed' => true, + 'send_email' => false, + 'values' => [ + 'Sign' => $this->getLogoBase64(), + ], + 'metadata' => [ + 'doc_type' => 'attestation', + 'reference' => $attestation->getReference(), + ], + ], + ], + ]); + + $submitterId = $result[0]['id'] ?? null; + + if (null === $submitterId) { + return false; + } + + $attestation->setSubmitterId($submitterId); + $this->em->flush(); + + return true; + } catch (\Throwable) { + return false; + } + } + + private function getLogoBase64(): string + { + $logoPath = $this->projectDir.'/public/logo.jpg'; + + if (!file_exists($logoPath)) { + return 'Association E-Cosplay'; + } + + return 'data:image/jpeg;base64,'.base64_encode(file_get_contents($logoPath)); + } + + /** + * Telecharge le PDF signe et le certificat depuis DocuSeal. + */ + public function downloadSignedDocuments(Attestation $attestation): bool + { + $submitterId = $attestation->getSubmitterId(); + + if (null === $submitterId) { + return false; + } + + try { + $submitter = $this->api->getSubmitter($submitterId); + $documents = $submitter['documents'] ?? []; + + if ([] === $documents) { + return false; + } + + $dir = $this->projectDir.'/var/rgpd/signed'; + if (!is_dir($dir)) { + mkdir($dir, 0775, true); + } + + $ref = $attestation->getReference(); + + // PDF signe + $pdfUrl = $documents[0]['url'] ?? null; + if (null !== $pdfUrl) { + $signedPath = $dir.'/'.$ref.'-signed.pdf'; + $content = file_get_contents($pdfUrl); + if (false !== $content && str_starts_with($content, '%PDF')) { + file_put_contents($signedPath, $content); + $attestation->setPdfFileSigned($signedPath); + } + } + + // Certificat d'audit + $auditUrl = $submitter['audit_log_url'] ?? null; + if (null !== $auditUrl) { + $certPath = $dir.'/'.$ref.'-certificat.pdf'; + $content = file_get_contents($auditUrl); + if (false !== $content) { + file_put_contents($certPath, $content); + $attestation->setPdfFileCertificate($certPath); + } + } + + $this->em->flush(); + + return null !== $attestation->getPdfFileSigned(); + } catch (\Throwable) { + return false; + } + } +} diff --git a/src/Service/KeycloakAdminService.php b/src/Service/KeycloakAdminService.php new file mode 100644 index 0000000..b631d41 --- /dev/null +++ b/src/Service/KeycloakAdminService.php @@ -0,0 +1,248 @@ +getToken(); + $tempPassword = bin2hex(random_bytes(8)); + + // Creer l'utilisateur + $response = $this->httpClient->request('POST', $this->getAdminUrl('/users'), [ + 'headers' => ['Authorization' => 'Bearer '.$token], + 'json' => [ + 'username' => $email, + 'email' => $email, + 'firstName' => $firstName, + 'lastName' => $lastName, + 'enabled' => true, + 'emailVerified' => true, + 'requiredActions' => ['UPDATE_PASSWORD', 'CONFIGURE_TOTP'], + 'credentials' => [ + [ + 'type' => 'password', + 'value' => $tempPassword, + 'temporary' => true, + ], + ], + ], + ]); + + if (201 !== $response->getStatusCode()) { + return ['created' => false, 'keycloakId' => null, 'tempPassword' => null]; + } + + // Recuperer l'ID du user cree + $locationHeader = $response->getHeaders(false)['location'][0] ?? ''; + $keycloakId = basename($locationHeader); + + if ('' === $keycloakId) { + $keycloakId = $this->getUserIdByEmail($email); + } + + return [ + 'created' => true, + 'keycloakId' => $keycloakId, + 'tempPassword' => $tempPassword, + ]; + } + + /** + * Ajouter un utilisateur a un groupe Keycloak. + */ + public function addUserToGroup(string $keycloakId, string $groupName): bool + { + $groupId = $this->getGroupIdByName($groupName); + + if (null === $groupId) { + return false; + } + + $token = $this->getToken(); + $response = $this->httpClient->request('PUT', $this->getAdminUrl('/users/'.$keycloakId.'/groups/'.$groupId), [ + 'headers' => ['Authorization' => 'Bearer '.$token], + ]); + + return 204 === $response->getStatusCode(); + } + + /** + * Supprimer un utilisateur de Keycloak. + */ + public function deleteUser(string $keycloakId): bool + { + $token = $this->getToken(); + $response = $this->httpClient->request('DELETE', $this->getAdminUrl('/users/'.$keycloakId), [ + 'headers' => ['Authorization' => 'Bearer '.$token], + ]); + + return 204 === $response->getStatusCode(); + } + + /** + * Mettre a jour les infos d'un utilisateur dans Keycloak. + */ + public function updateUser(string $keycloakId, string $firstName, string $lastName, string $email): bool + { + $token = $this->getToken(); + $response = $this->httpClient->request('PUT', $this->getAdminUrl('/users/'.$keycloakId), [ + 'headers' => ['Authorization' => 'Bearer '.$token], + 'json' => [ + 'firstName' => $firstName, + 'lastName' => $lastName, + 'email' => $email, + 'username' => $email, + ], + ]); + + return 204 === $response->getStatusCode(); + } + + /** + * Reset le mot de passe d'un utilisateur dans Keycloak. + */ + public function resetPassword(string $keycloakId, string $newPassword): bool + { + $token = $this->getToken(); + $response = $this->httpClient->request('PUT', $this->getAdminUrl('/users/'.$keycloakId.'/reset-password'), [ + 'headers' => ['Authorization' => 'Bearer '.$token], + 'json' => [ + 'type' => 'password', + 'value' => $newPassword, + 'temporary' => false, + ], + ]); + + return 204 === $response->getStatusCode(); + } + + /** + * Envoyer un email de reset password a l'utilisateur. + */ + public function sendResetPasswordEmail(string $keycloakId): bool + { + $token = $this->getToken(); + $response = $this->httpClient->request('PUT', $this->getAdminUrl('/users/'.$keycloakId.'/execute-actions-email'), [ + 'headers' => ['Authorization' => 'Bearer '.$token], + 'json' => ['UPDATE_PASSWORD'], + ]); + + return 200 === $response->getStatusCode() || 204 === $response->getStatusCode(); + } + + /** + * Recuperer les groupes d'un utilisateur. + * + * @return list + */ + public function getUserGroups(string $keycloakId): array + { + $token = $this->getToken(); + $response = $this->httpClient->request('GET', $this->getAdminUrl('/users/'.$keycloakId.'/groups'), [ + 'headers' => ['Authorization' => 'Bearer '.$token], + ]); + + $groups = $response->toArray(false); + + return array_map(fn (array $g) => $g['name'], $groups); + } + + /** + * Lister tous les utilisateurs du realm. + * + * @return list> + */ + public function listUsers(int $max = 100): array + { + $token = $this->getToken(); + $response = $this->httpClient->request('GET', $this->getAdminUrl('/users'), [ + 'headers' => ['Authorization' => 'Bearer '.$token], + 'query' => ['max' => $max], + ]); + + return $response->toArray(false); + } + + /** + * Recuperer l'ID d'un utilisateur par email. + */ + public function getUserIdByEmail(string $email): ?string + { + $token = $this->getToken(); + $response = $this->httpClient->request('GET', $this->getAdminUrl('/users'), [ + 'headers' => ['Authorization' => 'Bearer '.$token], + 'query' => ['email' => $email, 'exact' => 'true'], + ]); + + $users = $response->toArray(false); + + return $users[0]['id'] ?? null; + } + + private function getGroupIdByName(string $groupName): ?string + { + $token = $this->getToken(); + $response = $this->httpClient->request('GET', $this->getAdminUrl('/groups'), [ + 'headers' => ['Authorization' => 'Bearer '.$token], + 'query' => ['search' => $groupName, 'exact' => 'true'], + ]); + + $groups = $response->toArray(false); + + foreach ($groups as $group) { + if ($group['name'] === $groupName) { + return $group['id']; + } + } + + return null; + } + + private function getToken(): string + { + if (null !== $this->accessToken && null !== $this->tokenExpiresAt && time() < $this->tokenExpiresAt) { + return $this->accessToken; + } + + $response = $this->httpClient->request('POST', $this->keycloakUrl.'/realms/'.$this->realm.'/protocol/openid-connect/token', [ + 'body' => [ + 'client_id' => $this->clientId, + 'client_secret' => $this->clientSecret, + 'grant_type' => 'client_credentials', + ], + ]); + + $data = $response->toArray(); + $this->accessToken = $data['access_token']; + $this->tokenExpiresAt = time() + ($data['expires_in'] ?? 300) - 30; + + return $this->accessToken; + } + + private function getAdminUrl(string $path): string + { + return $this->keycloakUrl.'/admin/realms/'.$this->realm.$path; + } +} diff --git a/src/Service/MailerService.php b/src/Service/MailerService.php new file mode 100644 index 0000000..29edc41 --- /dev/null +++ b/src/Service/MailerService.php @@ -0,0 +1,131 @@ +adminEmail; + } + + public function getAdminFrom(): string + { + return 'Association E-Cosplay <'.$this->adminEmail.'>'; + } + + public function send(Email $email): void + { + $publicKeyPath = $this->projectDir.'/key.asc'; + + if (file_exists($publicKeyPath)) { + $email->attachFromPath($publicKeyPath, 'public_key.asc', 'application/pgp-keys'); + } + + $certificate = $this->projectDir.'/config/cert/smime/certificate.pem'; + $privateKey = $this->projectDir.'/config/cert/smime/private-key.pem'; + + if (file_exists($certificate) && file_exists($privateKey)) { + $signer = new SMimeSigner($certificate, $privateKey, $this->smimePassphrase); + $email = $signer->sign($email); + } + + $this->bus->dispatch(new SendEmailMessage($email)); + } + + /** + * @param array|null $attachments + */ + public function sendEmail(string $to, string $subject, string $content, ?string $from = null, ?string $replyTo = null, bool $withUnsubscribe = true, ?array $attachments = null): void + { + $from ??= $this->getAdminFrom(); + $canUnsubscribe = $withUnsubscribe && !$this->isWhitelisted($to); + + if ($canUnsubscribe && $this->unsubscribeManager->isUnsubscribed($to)) { + return; + } + + $email = (new Email()) + ->from($from) + ->to($to) + ->subject($subject) + ->html($content); + + if ($replyTo) { + $email->replyTo($replyTo); + } + + if ($attachments) { + foreach ($attachments as $attachment) { + $email->attachFromPath($attachment['path'], $attachment['name'] ?? null); + } + } + + $messageId = bin2hex(random_bytes(16)); + $email->getHeaders()->addIdHeader('Message-ID', $messageId.'@e-cosplay.fr'); + + $trackingUrl = $this->urlGenerator->generate('app_email_track', [ + 'messageId' => $messageId, + ], UrlGeneratorInterface::ABSOLUTE_URL); + + $viewUrl = $this->urlGenerator->generate('app_email_view', [ + 'messageId' => $messageId, + ], UrlGeneratorInterface::ABSOLUTE_URL); + + $html = $email->getHtmlBody(); + $html = str_replace('https://crm.e-cosplay.fr/logo.jpg', $trackingUrl, $html); + $html = str_replace('__VIEW_URL__', $viewUrl, $html); + $email->html($html); + + $tracking = new EmailTracking($messageId, $to, $subject, $html, $attachments); + $this->em->persist($tracking); + $this->em->flush(); + + if ($canUnsubscribe) { + $this->addUnsubscribeHeaders($email, $to); + } + + $this->send($email); + } + + private function isWhitelisted(string $email): bool + { + return strtolower(trim($email)) === strtolower($this->adminEmail); + } + + private function addUnsubscribeHeaders(Email $email, string $to): void + { + $token = $this->unsubscribeManager->generateToken($to); + + $unsubscribeUrl = $this->urlGenerator->generate('app_unsubscribe', [ + 'email' => $to, + 'token' => $token, + ], UrlGeneratorInterface::ABSOLUTE_URL); + + $email->getHeaders()->addTextHeader( + 'List-Unsubscribe', + sprintf('<%s>, ', $unsubscribeUrl, urlencode($to)) + ); + $email->getHeaders()->addTextHeader('List-Unsubscribe-Post', 'List-Unsubscribe=One-Click'); + } +} diff --git a/src/Service/MeilisearchService.php b/src/Service/MeilisearchService.php new file mode 100644 index 0000000..46ca629 --- /dev/null +++ b/src/Service/MeilisearchService.php @@ -0,0 +1,160 @@ +client = new Client($url, $apiKey); + } + + public function indexCustomer(Customer $customer): void + { + try { + $this->client->index('customer')->addDocuments([ + $this->serializeCustomer($customer), + ]); + } catch (\Throwable) { + } + } + + public function removeCustomer(int $customerId): void + { + try { + $this->client->index('customer')->deleteDocument($customerId); + } catch (\Throwable) { + } + } + + public function indexRevendeur(Revendeur $revendeur): void + { + try { + $this->client->index('reseller')->addDocuments([ + $this->serializeRevendeur($revendeur), + ]); + } catch (\Throwable) { + } + } + + public function removeRevendeur(int $revendeurId): void + { + try { + $this->client->index('reseller')->deleteDocument($revendeurId); + } catch (\Throwable) { + } + } + + /** + * @return list> + */ + public function searchCustomers(string $query, int $limit = 20): array + { + try { + $results = $this->client->index('customer')->search($query, ['limit' => $limit]); + + return $results->getHits(); + } catch (\Throwable) { + return []; + } + } + + /** + * @return list> + */ + public function searchRevendeurs(string $query, int $limit = 20): array + { + try { + $results = $this->client->index('reseller')->search($query, ['limit' => $limit]); + + return $results->getHits(); + } catch (\Throwable) { + return []; + } + } + + public function setupIndexes(): void + { + try { + $this->client->createIndex('customer', ['primaryKey' => 'id']); + } catch (\Throwable) { + } + $this->client->index('customer')->updateSearchableAttributes([ + 'firstName', 'lastName', 'raisonSociale', 'fullName', 'email', 'phone', 'siret', 'rcs', 'numTva', 'codeComptable', 'city', 'zipCode', 'address', + ]); + $this->client->index('customer')->updateFilterableAttributes([ + 'state', 'typeCompany', + ]); + + try { + $this->client->createIndex('reseller', ['primaryKey' => 'id']); + } catch (\Throwable) { + } + $this->client->index('reseller')->updateSearchableAttributes([ + 'firstName', 'lastName', 'fullName', 'raisonSociale', 'email', 'phone', 'siret', 'codeRevendeur', 'city', 'zipCode', 'address', + ]); + $this->client->index('reseller')->updateFilterableAttributes([ + 'isActive', + ]); + } + + /** + * @return array + */ + private function serializeCustomer(Customer $customer): array + { + return [ + 'id' => $customer->getId(), + 'firstName' => $customer->getFirstName(), + 'lastName' => $customer->getLastName(), + 'raisonSociale' => $customer->getRaisonSociale(), + 'fullName' => $customer->getFullName(), + 'email' => $customer->getEmail(), + 'phone' => $customer->getPhone(), + 'address' => $customer->getAddress(), + 'address2' => $customer->getAddress2(), + 'zipCode' => $customer->getZipCode(), + 'city' => $customer->getCity(), + 'siret' => $customer->getSiret(), + 'rcs' => $customer->getRcs(), + 'numTva' => $customer->getNumTva(), + 'codeComptable' => $customer->getCodeComptable(), + 'stripeCustomerId' => $customer->getStripeCustomerId(), + 'typeCompany' => $customer->getTypeCompany(), + 'state' => $customer->getState(), + ]; + } + + /** + * @return array + */ + private function serializeRevendeur(Revendeur $revendeur): array + { + return [ + 'id' => $revendeur->getId(), + 'codeRevendeur' => $revendeur->getCodeRevendeur(), + 'firstName' => $revendeur->getUser()->getFirstName(), + 'lastName' => $revendeur->getUser()->getLastName(), + 'fullName' => $revendeur->getUser()->getFullName(), + 'raisonSociale' => $revendeur->getRaisonSociale(), + 'email' => $revendeur->getEmail(), + 'phone' => $revendeur->getPhone(), + 'address' => $revendeur->getAddress(), + 'zipCode' => $revendeur->getZipCode(), + 'city' => $revendeur->getCity(), + 'siret' => $revendeur->getSiret(), + 'stripeConnectId' => $revendeur->getStripeConnectId(), + 'isUseStripe' => $revendeur->isUseStripe(), + 'isActive' => $revendeur->isActive(), + ]; + } +} diff --git a/src/Service/RgpdService.php b/src/Service/RgpdService.php new file mode 100644 index 0000000..581d36d --- /dev/null +++ b/src/Service/RgpdService.php @@ -0,0 +1,202 @@ +urlGenerator->generate('app_attestation_verify', [ + 'reference' => $attestation->getReference(), + ], UrlGeneratorInterface::ABSOLUTE_URL); + + $builder = new Builder( + writer: new PngWriter(), + data: $verifyUrl, + size: 200, + margin: 10, + ); + $result = $builder->build(); + + return 'data:image/png;base64,'.base64_encode($result->getString()); + } + + private function getVerifyUrl(Attestation $attestation): string + { + return $this->urlGenerator->generate('app_attestation_verify', [ + 'reference' => $attestation->getReference(), + ], UrlGeneratorInterface::ABSOLUTE_URL); + } + + /** + * @return array{found: bool, count: int} + */ + public function handleAccessRequest(string $ip, string $email): array + { + $ipHash = hash('sha256', $ip); + $visitors = $this->em->getRepository(AnalyticsUniqId::class)->findBy(['ipHash' => $ipHash]); + + if (0 === \count($visitors)) { + $attestation = new Attestation('no_data', $ip, $email, $this->hmacSecret); + $this->em->persist($attestation); + $this->em->flush(); + + $pdfPath = $this->generateNoDataPdf($ip, $attestation); + $attestation->setPdfFileUnsigned($pdfPath); + $this->em->flush(); + + $this->docuSealService->signAttestation($attestation); + + return ['found' => false, 'count' => 0]; + } + + $data = []; + foreach ($visitors as $visitor) { + $events = $this->em->getRepository(AnalyticsEvent::class)->findBy( + ['visitor' => $visitor], + ['createdAt' => 'DESC'], + ); + + $data[] = [ + 'visitor' => $visitor, + 'events' => $events, + ]; + } + + $attestation = new Attestation('access', $ip, $email, $this->hmacSecret); + $this->em->persist($attestation); + $this->em->flush(); + + $pdfPath = $this->generateAccessPdf($data, $ip, $attestation); + $attestation->setPdfFileUnsigned($pdfPath); + $this->em->flush(); + + $this->docuSealService->signAttestation($attestation); + + return ['found' => true, 'count' => \count($visitors)]; + } + + /** + * @return array{found: bool, deleted: int} + */ + public function handleDeletionRequest(string $ip, string $email): array + { + $ipHash = hash('sha256', $ip); + $visitors = $this->em->getRepository(AnalyticsUniqId::class)->findBy(['ipHash' => $ipHash]); + + if (0 === \count($visitors)) { + $attestation = new Attestation('no_data', $ip, $email, $this->hmacSecret); + $this->em->persist($attestation); + $this->em->flush(); + + $pdfPath = $this->generateNoDataPdf($ip, $attestation); + $attestation->setPdfFileUnsigned($pdfPath); + $this->em->flush(); + + $this->docuSealService->signAttestation($attestation); + + return ['found' => false, 'deleted' => 0]; + } + + $count = \count($visitors); + + foreach ($visitors as $visitor) { + $this->em->remove($visitor); + } + + $attestation = new Attestation('deletion', $ip, $email, $this->hmacSecret); + $this->em->persist($attestation); + $this->em->flush(); + + $pdfPath = $this->generateDeletionPdf($ip, $attestation); + $attestation->setPdfFileUnsigned($pdfPath); + $this->em->flush(); + + $this->docuSealService->signAttestation($attestation); + + return ['found' => true, 'deleted' => $count]; + } + + private function getLogoBase64(): string + { + $logoPath = $this->projectDir.'/public/logo.jpg'; + + return file_exists($logoPath) ? 'data:image/jpeg;base64,'.base64_encode((string) file_get_contents($logoPath)) : ''; + } + + private function getPdfVars(string $ip, Attestation $attestation): array + { + return [ + 'ip' => $ip, + 'logo' => $this->getLogoBase64(), + 'date' => $attestation->getCreatedAt(), + 'attestation' => $attestation, + 'qrcode' => $this->generateQrCodeBase64($attestation), + 'verify_url' => $this->getVerifyUrl($attestation), + ]; + } + + /** + * @param list}> $data + */ + private function generateAccessPdf(array $data, string $ip, Attestation $attestation): string + { + $vars = $this->getPdfVars($ip, $attestation); + $vars['data'] = $data; + + return $this->renderPdf($this->twig->render('pdf/rgpd_access.html.twig', $vars)); + } + + private function generateNoDataPdf(string $ip, Attestation $attestation): string + { + return $this->renderPdf($this->twig->render('pdf/rgpd_no_data.html.twig', $this->getPdfVars($ip, $attestation))); + } + + private function generateDeletionPdf(string $ip, Attestation $attestation): string + { + return $this->renderPdf($this->twig->render('pdf/rgpd_deletion.html.twig', $this->getPdfVars($ip, $attestation))); + } + + private function renderPdf(string $html): string + { + $dir = $this->projectDir.'/var/rgpd'; + if (!is_dir($dir)) { + mkdir($dir, 0775, true); + } + + $dompdf = new Dompdf(); + $dompdf->loadHtml($html); + $dompdf->setPaper('A4'); + $dompdf->render(); + + $path = $dir.'/'.bin2hex(random_bytes(16)).'.pdf'; + file_put_contents($path, $dompdf->output()); + + return $path; + } +} diff --git a/src/Service/UnsubscribeManager.php b/src/Service/UnsubscribeManager.php new file mode 100644 index 0000000..33fea4f --- /dev/null +++ b/src/Service/UnsubscribeManager.php @@ -0,0 +1,74 @@ +storagePath = $projectDir.'/var/unsubscribed.json'; + $this->secret = $appSecret; + } + + public function generateToken(string $email): string + { + return hash_hmac('sha256', strtolower(trim($email)), $this->secret); + } + + public function isValidToken(string $email, string $token): bool + { + return hash_equals($this->generateToken($email), $token); + } + + public function isUnsubscribed(string $email): bool + { + $hash = $this->generateToken($email); + + return \in_array($hash, $this->loadHashes(), true); + } + + public function unsubscribe(string $email): void + { + $hash = $this->generateToken($email); + $hashes = $this->loadHashes(); + + if (!\in_array($hash, $hashes, true)) { + $hashes[] = $hash; + $this->saveHashes($hashes); + } + } + + /** + * @return list + */ + private function loadHashes(): array + { + if (!file_exists($this->storagePath)) { + return []; + } + + $data = json_decode(file_get_contents($this->storagePath), true); + + return \is_array($data) ? $data : []; + } + + /** + * @param list $hashes + */ + private function saveHashes(array $hashes): void + { + $dir = \dirname($this->storagePath); + if (!is_dir($dir)) { + mkdir($dir, 0o755, true); + } + + file_put_contents($this->storagePath, json_encode($hashes), \LOCK_EX); + } +} diff --git a/src/Twig/ViteAssetExtension.php b/src/Twig/ViteAssetExtension.php new file mode 100644 index 0000000..779a084 --- /dev/null +++ b/src/Twig/ViteAssetExtension.php @@ -0,0 +1,124 @@ +>|null */ + private ?array $manifestData = null; + private readonly bool $isDev; + + public function __construct( + private readonly string $manifest, + private readonly CacheItemPoolInterface $cache, + private readonly ?ContentSecurityPolicyListener $cspListener = null, + ) { + $this->isDev = '0' === $_ENV['VITE_LOAD']; + } + + /** @return list */ + public function getFunctions(): array + { + return [ + new TwigFunction('vite_asset', $this->asset(...), ['is_safe' => ['html']]), + new TwigFunction('isMobile', $this->isMobile(...), ['is_safe' => ['html']]), + new TwigFunction('vite_favicons', $this->favicons(...), ['is_safe' => ['html']]), + ]; + } + + protected function getNonce(): string + { + return $this->cspListener?->getNonce('script') ?? ''; + } + + public function isMobile(): bool + { + $detect = new MobileDetect(); + + return $detect->isMobile() || $detect->isTablet(); + } + + private function loadManifest(): void + { + if (null === $this->manifestData) { + $item = $this->cache->getItem(self::CACHE_KEY); + if ($item->isHit()) { + $this->manifestData = $item->get(); + } else { + if (!file_exists($this->manifest)) { + $this->manifestData = []; + + return; + } + $this->manifestData = json_decode((string) file_get_contents($this->manifest), true); + $item->set($this->manifestData); + $this->cache->save($item); + } + } + } + + /** + * @param list $deps + */ + public function asset(string $entry, array $deps = []): string + { + return $this->isDev ? $this->assetDev($entry, $deps) : $this->assetProd($entry); + } + + /** + * @param list $deps + */ + public function assetDev(string $entry, array $deps): string + { + $nonce = $this->getNonce(); + + return << + +HTML; + } + + public function assetProd(string $entry): string + { + $this->loadManifest(); + $nonce = $this->getNonce(); + + $file = $this->manifestData[$entry]['file'] ?? ''; + $css = $this->manifestData[$entry]['css'] ?? []; + + $html = << +HTML; + + foreach ($css as $cssFile) { + $html .= ''; + } + + return $html; + } + + public function favicons(): string + { + return $this->isDev ? '' : $this->faviconsProd(); + } + + private function faviconsProd(): string + { + $this->loadManifest(); + $faviconHtml = ''; + foreach ($this->manifestData as $key => $favicon) { + if (!str_contains($key, '.js') && isset($favicon['file'])) { + $faviconHtml .= ''; + } + } + + return $faviconHtml; + } +} diff --git a/symfony.lock b/symfony.lock index 49d282a..e0e89f9 100644 --- a/symfony.lock +++ b/symfony.lock @@ -35,6 +35,98 @@ "migrations/.gitignore" ] }, + "endroid/qr-code-bundle": { + "version": "6.1.0" + }, + "friendsofphp/php-cs-fixer": { + "version": "3.94", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "3.39", + "ref": "97aaf9026490db73b86c23d49e5774bc89d2b232" + }, + "files": [ + ".php-cs-fixer.dist.php" + ] + }, + "knplabs/knp-paginator-bundle": { + "version": "v6.10.0" + }, + "knpuniversity/oauth2-client-bundle": { + "version": "2.20", + "recipe": { + "repo": "github.com/symfony/recipes-contrib", + "branch": "main", + "version": "1.20", + "ref": "1ff300d8c030f55c99219cc55050b97a695af3f6" + }, + "files": [ + "config/packages/knpu_oauth2_client.yaml" + ] + }, + "league/flysystem-bundle": { + "version": "3.6", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "1.0", + "ref": "913dc3d7a5a1af0d2b044c5ac3a16e2f851d7380" + }, + "files": [ + "config/packages/flysystem.yaml", + "var/storage/.gitignore" + ] + }, + "liip/imagine-bundle": { + "version": "2.17", + "recipe": { + "repo": "github.com/symfony/recipes-contrib", + "branch": "main", + "version": "1.8", + "ref": "d1227d002b70d1a1f941d91845fcd7ac7fbfc929" + }, + "files": [ + "config/packages/liip_imagine.yaml", + "config/routes/liip_imagine.yaml" + ] + }, + "nelmio/security-bundle": { + "version": "3.9", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "2.4", + "ref": "71045833e4f882ad9de8c95fe47efb99a1eec2f7" + }, + "files": [ + "config/packages/nelmio_security.yaml" + ] + }, + "php-http/discovery": { + "version": "1.20", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "1.18", + "ref": "f45b5dd173a27873ab19f5e3180b2f661c21de02" + }, + "files": [ + "config/packages/http_discovery.yaml" + ] + }, + "phpstan/phpstan": { + "version": "2.1", + "recipe": { + "repo": "github.com/symfony/recipes-contrib", + "branch": "main", + "version": "1.0", + "ref": "5e490cc197fb6bb1ae22e5abbc531ddc633b6767" + }, + "files": [ + "phpstan.dist.neon" + ] + }, "phpunit/phpunit": { "version": "13.0", "recipe": { @@ -50,6 +142,52 @@ "bin/phpunit" ] }, + "scheb/2fa-bundle": { + "version": "8.5", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "6.0", + "ref": "1e6f68089146853a790b5da9946fc5974f6fcd49" + }, + "files": [ + "config/packages/scheb_2fa.yaml", + "config/routes/scheb_2fa.yaml" + ] + }, + "spomky-labs/pwa-bundle": { + "version": "1.5", + "recipe": { + "repo": "github.com/symfony/recipes-contrib", + "branch": "main", + "version": "1.3", + "ref": "ac33d9498ab25856f16d1d7df9ad4b9b0cad066d" + }, + "files": [ + "config/packages/pwa.yaml" + ] + }, + "stripe/stripe-php": { + "version": "20.0", + "recipe": { + "repo": "github.com/symfony/recipes-contrib", + "branch": "main", + "version": "19.0", + "ref": "d6829c693e3927a8972c7671d74a1a5c505712b0" + }, + "files": [ + "config/packages/stripe.yaml" + ] + }, + "symfony/amazon-mailer": { + "version": "8.0", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "4.4", + "ref": "9648db3ecae5c8a6b1a5f74715d3907124348815" + } + }, "symfony/asset-mapper": { "version": "8.0", "recipe": { @@ -229,21 +367,6 @@ "config/routes/security.yaml" ] }, - "symfony/stimulus-bundle": { - "version": "2.34", - "recipe": { - "repo": "github.com/symfony/recipes", - "branch": "main", - "version": "2.24", - "ref": "3357f2fa6627b93658d8e13baa416b2a94a50c5f" - }, - "files": [ - "assets/controllers.json", - "assets/controllers/csrf_protection_controller.js", - "assets/controllers/hello_controller.js", - "assets/stimulus_bootstrap.js" - ] - }, "symfony/translation": { "version": "8.0", "recipe": { @@ -270,18 +393,6 @@ "templates/base.html.twig" ] }, - "symfony/ux-turbo": { - "version": "2.34", - "recipe": { - "repo": "github.com/symfony/recipes", - "branch": "main", - "version": "2.20", - "ref": "287f7c6eb6e9b65e422d34c00795b360a787380b" - }, - "files": [ - "config/packages/ux_turbo.yaml" - ] - }, "symfony/validator": { "version": "8.0", "recipe": { @@ -321,5 +432,17 @@ }, "twig/extra-bundle": { "version": "v3.24.0" + }, + "vich/uploader-bundle": { + "version": "2.9", + "recipe": { + "repo": "github.com/symfony/recipes-contrib", + "branch": "main", + "version": "1.13", + "ref": "1b3064c2f6b255c2bc2f56461aaeb76b11e07e36" + }, + "files": [ + "config/packages/vich_uploader.yaml" + ] } } diff --git a/templates/admin/_layout.html.twig b/templates/admin/_layout.html.twig new file mode 100644 index 0000000..8ffb199 --- /dev/null +++ b/templates/admin/_layout.html.twig @@ -0,0 +1,132 @@ +{% extends 'base.html.twig' %} + +{% block body_class %}{% endblock %} +{% block header %}{% endblock %} +{% block main_class %}flex{% endblock %} +{% block stylesheets %} + +{% endblock %} + +{% set current_route = app.request.attributes.get('_route') %} + +{% block body %} + + + +
+ +
+
+ + Admin + +
+ + {% block admin_content %}{% endblock %} +
+ +{% endblock %} + +{% block footer %}{% endblock %} diff --git a/templates/admin/clients/create.html.twig b/templates/admin/clients/create.html.twig new file mode 100644 index 0000000..72e6e48 --- /dev/null +++ b/templates/admin/clients/create.html.twig @@ -0,0 +1,127 @@ +{% extends 'admin/_layout.html.twig' %} + +{% block title %}Nouveau client - Administration - CRM Ecosplay{% endblock %} + +{% block admin_content %} +
+
+

Nouveau client

+ Retour +
+ + {% for type, messages in app.flashes %} + {% for message in messages %} +
+ {{ message }} +
+ {% endfor %} + {% endfor %} + +
+ +
+

Identite

+
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+ +
+

Entreprise

+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ +
+

Adresse

+
+
+ + +
+
+ + +
+
+
+ + +
+
+ + +
+
+
+
+ +
+

Le client sera automatiquement cree chez Stripe. Un mot de passe temporaire sera genere pour son acces a l'Espace Client.

+ +
+
+
+{% endblock %} diff --git a/templates/admin/clients/index.html.twig b/templates/admin/clients/index.html.twig new file mode 100644 index 0000000..3ec88e4 --- /dev/null +++ b/templates/admin/clients/index.html.twig @@ -0,0 +1,100 @@ +{% extends 'admin/_layout.html.twig' %} + +{% block title %}Clients - Administration - CRM Ecosplay{% endblock %} + +{% block admin_content %} +
+
+

Clients

+ + Nouveau client +
+ + {% for type, messages in app.flashes %} + {% for message in messages %} +
+ {{ message }} +
+ {% endfor %} + {% endfor %} + + {# Barre de recherche #} +
+
+ + +
+ +
+ + {# Liste des clients #} +
+ + + + + + + + + + + + + + + {% for customer in customers %} + + + + + + + + + + + {% else %} + + + + {% endfor %} + +
ClientEmailTypeSIRETStripeStatutInscriptionActions
+ {{ customer.fullName }} + {% if customer.raisonSociale and customer.firstName %} + {{ customer.firstName }} {{ customer.lastName }} + {% endif %} + {{ customer.email ?? customer.user.email }} + {% if customer.typeCompany %} + {{ customer.typeCompany }} + {% else %} + + {% endif %} + {{ customer.siret ?? '—' }} + {% if customer.stripeCustomerId %} + Lie + {% else %} + Non + {% endif %} + + {% if customer.state == 'active' %} + Actif + {% elseif customer.state == 'suspended' %} + Suspendu + {% else %} + Desactive + {% endif %} + {{ customer.createdAt|date('d/m/Y') }} +
+ {% if customer.isActive %} + + {% else %} + + {% endif %} +
+
Aucun client.
+
+

{{ customers|length }} client(s)

+
+{% endblock %} diff --git a/templates/admin/dashboard.html.twig b/templates/admin/dashboard.html.twig new file mode 100644 index 0000000..6740d2b --- /dev/null +++ b/templates/admin/dashboard.html.twig @@ -0,0 +1,23 @@ +{% extends 'admin/_layout.html.twig' %} + +{% block title %}Administration - CRM Ecosplay{% endblock %} + +{% block admin_content %} +
+

Tableau de bord

+ +
+
+

Bienvenue, {{ app.user.fullName }}.

+

+ Role : + {% if is_granted('ROLE_ROOT') %} + Super Admin + {% else %} + Employe + {% endif %} +

+
+
+
+{% endblock %} diff --git a/templates/admin/facturation/index.html.twig b/templates/admin/facturation/index.html.twig new file mode 100644 index 0000000..c6828a7 --- /dev/null +++ b/templates/admin/facturation/index.html.twig @@ -0,0 +1,9 @@ +{% extends 'admin/_layout.html.twig' %} + +{% block title %}Facturation - Administration - CRM Ecosplay{% endblock %} + +{% block admin_content %} +
+

Facturation

+
+{% endblock %} diff --git a/templates/admin/membres.html.twig b/templates/admin/membres.html.twig new file mode 100644 index 0000000..cc51520 --- /dev/null +++ b/templates/admin/membres.html.twig @@ -0,0 +1,181 @@ +{% extends 'admin/_layout.html.twig' %} + +{% block title %}Administration - Membres - CRM Ecosplay{% endblock %} + +{% block admin_content %} +
+

Administration

+ + {% for type, messages in app.flashes %} + {% for message in messages %} +
+ {{ message }} +
+ {% endfor %} + {% endfor %} + +
+ +
+

Creer un administrateur

+
+
+
+
+ + +
+
+ + +
+
+
+ + +
+
+ +
+ + + + + + + + + +
+
+

Le mot de passe sera genere automatiquement et envoye par email au nouveau membre.

+ +
+
+
+ +
+

Membres (Keycloak)

+
+ + + + + + + + + + + + + + + {% for membre in membres %} + + + + + + + + + + + {% else %} + + + + {% endfor %} + +
NomEmailStatutEmail verifieSynchro BDDRole localInscriptionActions
{{ membre.firstName }} {{ membre.lastName }}{{ membre.email }} + {% if membre.enabled %} + Actif + {% else %} + Desactive + {% endif %} + + {% if membre.emailVerified %} + + {% else %} + + {% endif %} + + {% if membre.existsInDb %} + Synchronise + {% else %} + Non synchronise + {% endif %} + + {% if membre.localUser %} + {% if 'ROLE_ROOT' in membre.localUser.roles %} + Super Admin + {% elseif 'ROLE_EMPLOYE' in membre.localUser.roles %} + Employe + {% else %} + User + {% endif %} + {% else %} + + {% endif %} + + {% if membre.createdTimestamp > 0 %} + {{ '@' ~ (membre.createdTimestamp // 1000)|date('d/m/Y') }} + {% else %} + — + {% endif %} + +
+ {% if membre.existsInDb and membre.localUser and membre.localUser.hasTempPassword %} +
+ +
+ {% endif %} +
+ +
+
+
Aucun membre trouve.
+
+

{{ membres|length }} membre(s) au total

+
+ +
+
+{% endblock %} diff --git a/templates/admin/profil/index.html.twig b/templates/admin/profil/index.html.twig new file mode 100644 index 0000000..4f3b8f5 --- /dev/null +++ b/templates/admin/profil/index.html.twig @@ -0,0 +1,187 @@ +{% extends 'admin/_layout.html.twig' %} + +{% block title %}Mon profil - Administration - CRM Ecosplay{% endblock %} + +{% block admin_content %} +
+

Mon profil

+ + {% for type, messages in app.flashes %} + {% for message in messages %} +
+ {{ message }} +
+ {% endfor %} + {% endfor %} + +
+ + {# Informations du profil (lecture seule) #} +
+

Informations

+
+
+ {% if app.user.avatar %} + Avatar + {% else %} +
+ {{ app.user.firstName|first }}{{ app.user.lastName|first }} +
+ {% endif %} +
+

{{ app.user.fullName }}

+

{{ app.user.email }}

+ {% if is_granted('ROLE_ROOT') %} + Super Admin + {% else %} + Employe + {% endif %} +
+
+ +
+ + + + + + + + + + + + + + + + + + + + + +
Prenom{{ app.user.firstName }}
Nom{{ app.user.lastName }}
Email{{ app.user.email }}
Connexion + {% if app.user.keycloakId %} + Keycloak + {% else %} + Local + {% endif %} +
Inscrit le{{ app.user.createdAt|date('d/m/Y a H:i') }}
+
+ + {% if not is_granted('ROLE_ROOT') %} +

Le prenom, nom et email ne peuvent pas etre modifies. Contactez un administrateur pour toute modification.

+ {% endif %} +
+
+ + {# Avatar #} +
+

Avatar

+
+
+ {% if app.user.avatar %} + Avatar + {% else %} +
+ {{ app.user.firstName|first }}{{ app.user.lastName|first }} +
+ {% endif %} +
+
+
+ + +
+ +
+ {% if app.user.avatar %} +
+ +
+ {% endif %} +
+
+
+
+ + {# Modifier les informations (ROLE_ROOT uniquement) #} + {% if is_granted('ROLE_ROOT') %} +
+

Modifier mes informations

+
+
+
+
+ + +
+
+ + +
+
+
+ + +
+ {% if app.user.keycloakId %} +

Les modifications seront egalement appliquees dans Keycloak.

+ {% endif %} + +
+
+
+ {% endif %} + + {# Changer le mot de passe #} +
+

Changer le mot de passe

+
+
+
+ + +
+
+
+ + +
+
+ + +
+
+ {% if app.user.keycloakId %} +

Le mot de passe sera egalement mis a jour dans Keycloak.

+ {% endif %} + +
+
+
+ +
+
+{% endblock %} diff --git a/templates/admin/revendeurs/create.html.twig b/templates/admin/revendeurs/create.html.twig new file mode 100644 index 0000000..9237711 --- /dev/null +++ b/templates/admin/revendeurs/create.html.twig @@ -0,0 +1,104 @@ +{% extends 'admin/_layout.html.twig' %} + +{% block title %}Nouveau revendeur - Administration - CRM Ecosplay{% endblock %} + +{% block admin_content %} +
+
+

Nouveau revendeur

+ Retour +
+ + {% for type, messages in app.flashes %} + {% for message in messages %} +
+ {{ message }} +
+ {% endfor %} + {% endfor %} + +
+ +
+

Identite

+
+
+ + +
+
+ + +
+
+ + +
+
+
+ +
+

Entreprise

+
+
+ + +
+
+ + +
+
+ + +
+
+
+ +
+

Adresse

+
+
+ + +
+
+
+ + +
+
+ + +
+
+
+
+ +
+

Options

+ +
+ +
+

Le code revendeur (EC-XXXX) sera genere automatiquement. Le mot de passe temporaire sera envoye par email.

+ +
+
+
+{% endblock %} diff --git a/templates/admin/revendeurs/edit.html.twig b/templates/admin/revendeurs/edit.html.twig new file mode 100644 index 0000000..2bc8246 --- /dev/null +++ b/templates/admin/revendeurs/edit.html.twig @@ -0,0 +1,90 @@ +{% extends 'admin/_layout.html.twig' %} + +{% block title %}Modifier {{ revendeur.codeRevendeur }} - Administration - CRM Ecosplay{% endblock %} + +{% block admin_content %} +
+
+

Modifier le revendeur

+ Retour +
+ + {% for type, messages in app.flashes %} + {% for message in messages %} +
+ {{ message }} +
+ {% endfor %} + {% endfor %} + +
{{ revendeur.codeRevendeur }}
+ +
+ +
+

Entreprise

+
+
+ + +
+
+ + +
+
+ + +
+
+
+ + +
+
+ +
+

Adresse

+
+
+ + +
+
+
+ + +
+
+ + +
+
+
+
+ +
+

Options

+ + {% if revendeur.stripeConnectId %} +

Stripe Connect ID : {{ revendeur.stripeConnectId }}

+ {% endif %} +
+ + +
+
+{% endblock %} diff --git a/templates/admin/revendeurs/index.html.twig b/templates/admin/revendeurs/index.html.twig new file mode 100644 index 0000000..2d115e8 --- /dev/null +++ b/templates/admin/revendeurs/index.html.twig @@ -0,0 +1,97 @@ +{% extends 'admin/_layout.html.twig' %} + +{% block title %}Revendeurs - Administration - CRM Ecosplay{% endblock %} + +{% block admin_content %} +
+
+

Revendeurs

+ + Nouveau revendeur +
+ + {% for type, messages in app.flashes %} + {% for message in messages %} +
+ {{ message }} +
+ {% endfor %} + {% endfor %} + + {# Recherche #} +
+ + + +
+ + {# Liste #} +
+ + + + + + + + + + + + + + + + {% for revendeur in revendeurs %} + + + + + + + + + + + + {% else %} + + + + {% endfor %} + +
CodeNomRaison socialeEmailSIRETStripeStatutInscriptionActions
+ {{ revendeur.codeRevendeur }} + {{ revendeur.user.fullName }}{{ revendeur.raisonSociale ?? '—' }}{{ revendeur.email ?? revendeur.user.email }}{{ revendeur.siret ?? '—' }} + {% if revendeur.isUseStripe %} + {% if revendeur.stripeConnectId %} + Connecte + {% else %} + En attente + {% endif %} + {% else %} + Desactive + {% endif %} + + {% if revendeur.isActive %} + Actif + {% else %} + Inactif + {% endif %} + {{ revendeur.createdAt|date('d/m/Y') }} +
+ Modifier + Contrat +
+ {% if revendeur.isActive %} + + {% else %} + + {% endif %} +
+
+
Aucun revendeur.
+
+

{{ revendeurs|length }} revendeur(s)

+
+{% endblock %} diff --git a/templates/admin/services/index.html.twig b/templates/admin/services/index.html.twig new file mode 100644 index 0000000..dc9343b --- /dev/null +++ b/templates/admin/services/index.html.twig @@ -0,0 +1,9 @@ +{% extends 'admin/_layout.html.twig' %} + +{% block title %}Services - Administration - CRM Ecosplay{% endblock %} + +{% block admin_content %} +
+

Services

+
+{% endblock %} diff --git a/templates/admin/stats/index.html.twig b/templates/admin/stats/index.html.twig new file mode 100644 index 0000000..93cd920 --- /dev/null +++ b/templates/admin/stats/index.html.twig @@ -0,0 +1,164 @@ +{% extends 'admin/_layout.html.twig' %} + +{% block title %}Statistiques - Administration - CRM Ecosplay{% endblock %} + +{% block admin_content %} +
+

Statistiques

+ + {# Selecteur de periode #} +
+
+
+ + +
+
+
+ + +
+
+ + +
+
+ +
+

Periode : {{ dateFrom }} — {{ dateTo }}

+
+ + {# KPI Global #} +
+
+

CA HT

+

{{ global.ca_ht|number_format(2, ',', ' ') }} €

+
+
+

TVA

+

{{ global.ca_tva|number_format(2, ',', ' ') }} €

+
+
+

CA TTC

+

{{ global.ca_ttc|number_format(2, ',', ' ') }} €

+
+
+
+
+

Cout total

+

{{ global.cout_total|number_format(2, ',', ' ') }} €

+
+
+

Commission Stripe

+

{{ global.commission_stripe|number_format(2, ',', ' ') }} €

+
+
+

Marge nette HT

+

{{ (global.ca_ht - global.cout_total - global.commission_stripe)|number_format(2, ',', ' ') }} €

+
+
+ + {# CA par service #} +

Chiffre d'affaires par service

+
+ {% for service in services %} +
+
+ {{ service.name }} + {{ service.clients }} client(s) +
+
+
+
+

HT

+

{{ service.ca_ht|number_format(0, ',', ' ') }} €

+
+
+

TVA

+

{{ service.ca_tva|number_format(0, ',', ' ') }} €

+
+
+

TTC

+

{{ service.ca_ttc|number_format(0, ',', ' ') }} €

+
+
+
+ {% if service.cout is defined %} +
+
+ Cout + {{ service.cout|number_format(0, ',', ' ') }} € +
+
+ Marge + {{ (service.ca_ht - service.cout)|number_format(0, ',', ' ') }} € +
+ {% if service.ca_ht > service.cout %} + Rentable + {% else %} + Non rentable + {% endif %} +
+ {% endif %} + {% set ratio = global.ca_ht > 0 ? (service.ca_ht / global.ca_ht * 100) : 0 %} +
+
+
+
+

{{ ratio|number_format(1) }}% du CA total — {{ service.abonnements }} abonnement(s)

+
+
+ {% endfor %} +
+ + {# Repartition visuelle #} +
+

Repartition du CA HT par service

+
+ {% for service in services %} + {% set ratio = global.ca_ht > 0 ? (service.ca_ht / global.ca_ht * 100) : 0 %} +
+ {% endfor %} +
+
+ {% for service in services %} +
+ + {{ service.name }} +
+ {% endfor %} +
+
+ + {# Evolution mensuelle - en bas #} +
+

Evolution CA HT (6 mois)

+
+ {% set max_ca = 0 %} + {% for month in evolution %} + {% if month.ca_ht > max_ca %}{% set max_ca = month.ca_ht %}{% endif %} + {% endfor %} + {% for month in evolution %} + {% set height = max_ca > 0 ? (month.ca_ht / max_ca * 100) : 0 %} +
+ {{ month.ca_ht|number_format(0, ',', ' ') }}€ +
+ {{ month.month }} +
+ {% endfor %} +
+
+
+{% endblock %} diff --git a/templates/admin/status/index.html.twig b/templates/admin/status/index.html.twig new file mode 100644 index 0000000..20649c9 --- /dev/null +++ b/templates/admin/status/index.html.twig @@ -0,0 +1,186 @@ +{% extends 'admin/_layout.html.twig' %} + +{% block title %}Status des services - Administration - CRM Ecosplay{% endblock %} + +{% block admin_content %} +
+
+

Status des services

+ Gerer les services +
+ + {% for type, messages in app.flashes %} + {% for message in messages %} +
+ {{ message }} +
+ {% endfor %} + {% endfor %} + + {# Messages actifs #} + {% if activeMessages|length > 0 %} +
+

Messages actifs

+
+ {% for msg in activeMessages %} +
+
+ {{ msg.severity }} + {{ msg.service.name }} + — {{ msg.title }} + {{ msg.createdAt|date('d/m H:i') }} +
+
+ +
+
+ {% endfor %} +
+
+ {% endif %} + + {# Formulaire nouveau message #} +
+

Publier un message

+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
+
+ +
+ {% for data in servicesData %} +
+

{{ data.category.name }}

+ +
+ {% for item in data.services %} + {% set service = item.service %} + {% set uptime = item.uptime %} + {% set dailyStatus = item.dailyStatus %} + +
+
+
+ +
+ {{ service.name }} + {% if service.url %} + {{ service.url }} + {% endif %} +
+
+
+ {{ service.status }} + {{ uptime }}% +
+
+ + {% if service.message %} +
+ {{ service.message }} +
+ {% endif %} + + {# Graphique uptime 30 jours #} +
+ {% for day in dailyStatus %} +
+ {% endfor %} +
+
+ 30 jours + Aujourd'hui +
+ + {# Formulaire de mise a jour #} +
+
+
+ + +
+
+ + +
+ +
+
+
+ {% endfor %} +
+
+ {% else %} +

Aucun service configure.

+ {% endfor %} +
+
+{% endblock %} diff --git a/templates/admin/status/manage.html.twig b/templates/admin/status/manage.html.twig new file mode 100644 index 0000000..4ee6bf5 --- /dev/null +++ b/templates/admin/status/manage.html.twig @@ -0,0 +1,137 @@ +{% extends 'admin/_layout.html.twig' %} + +{% block title %}Gerer les services - Administration - CRM Ecosplay{% endblock %} + +{% block admin_content %} +
+
+

Gerer les services

+ Retour au status +
+ + {% for type, messages in app.flashes %} + {% for message in messages %} +
+ {{ message }} +
+ {% endfor %} + {% endfor %} + +
+ + {# Creer une categorie #} +
+

Nouvelle categorie

+
+
+
+ + +
+
+ + +
+ +
+
+
+ + {# Liste des categories + services #} + {% for category in categories %} +
+
+

{{ category.name }}

+
+ {{ category.services|length }} service(s) +
+ +
+
+
+ + {# Services existants #} + {% if category.services|length > 0 %} +
+ + + + + + + + + + + {% for service in category.services %} + + + + + + + {% endfor %} + +
ServiceURLStatusActions
{{ service.name }}{{ service.url ?? '—' }} + + +
+ +
+
+
+ {% endif %} + + {# Creer un service dans cette categorie #} +
+
+ +
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
+
+
+ {% else %} +

Aucune categorie. Creez-en une pour commencer.

+ {% endfor %} + +
+
+{% endblock %} diff --git a/templates/admin/sync/index.html.twig b/templates/admin/sync/index.html.twig new file mode 100644 index 0000000..934da58 --- /dev/null +++ b/templates/admin/sync/index.html.twig @@ -0,0 +1,78 @@ +{% extends 'admin/_layout.html.twig' %} + +{% block title %}Synchronisation - Administration - CRM Ecosplay{% endblock %} + +{% block admin_content %} +
+

Synchronisation

+ + {% for type, messages in app.flashes %} + {% for message in messages %} +
+ {{ message }} +
+ {% endfor %} + {% endfor %} + +
+ + {# Sync tout #} +
+
+
+

Synchronisation complete

+

Reindexe tous les clients et revendeurs dans Meilisearch.

+
+
+ +
+
+
+ + {# Sync clients #} +
+
+
+
+ +
+
+

Clients

+

Index Meilisearch : customer

+

{{ totalCustomers }} client(s) en base

+
+
+
+ +
+
+
+ + {# Sync revendeurs #} +
+
+
+
+ +
+
+

Revendeurs

+

Index Meilisearch : reseller

+

{{ totalRevendeurs }} revendeur(s) en base

+
+
+
+ +
+
+
+ +
+
+{% endblock %} diff --git a/templates/attestation/not_found.html.twig b/templates/attestation/not_found.html.twig new file mode 100644 index 0000000..8f121ea --- /dev/null +++ b/templates/attestation/not_found.html.twig @@ -0,0 +1,18 @@ +{% extends 'legal/_layout.html.twig' %} + +{% block title %}Attestation introuvable - CRM Ecosplay{% endblock %} + +{% block body %} +
+

Attestation introuvable

+ +
+
+ + Reference inconnue +
+

Aucune attestation n'a ete trouvee pour la reference {{ reference }}.

+

Verifiez que le lien ou le QR code est correct.

+
+
+{% endblock %} diff --git a/templates/attestation/verify.html.twig b/templates/attestation/verify.html.twig new file mode 100644 index 0000000..98b8e31 --- /dev/null +++ b/templates/attestation/verify.html.twig @@ -0,0 +1,89 @@ +{% extends 'legal/_layout.html.twig' %} + +{% block title %}Verification attestation {{ attestation.reference }} - CRM Ecosplay{% endblock %} +{% block description %}Verification de l'attestation RGPD {{ attestation.reference }}.{% endblock %} + +{% block body %} +
+

Verification d'attestation

+ +
+ + {% if valid %} +
+
+ + Attestation valide +
+

La signature HMAC-SHA256 de ce document a ete verifiee avec succes. Ce document est authentique.

+
+ {% else %} +
+
+ + Signature invalide +
+

La signature de ce document n'a pas pu etre verifiee. Ce document a peut-etre ete modifie.

+
+ {% endif %} + +
+
+ Details de l'attestation +
+
+ + + + + + + + + + + + + + + + + + + + + +
Reference{{ attestation.reference }}
Type + {% if attestation.type == 'access' %} + Droit d'acces + {% elseif attestation.type == 'deletion' %} + Suppression + {% else %} + Absence de donnees + {% endif %} +
Date{{ attestation.createdAt|date('d/m/Y a H:i:s') }}
Adresse IP{{ attestation.ip }}
Email{{ attestation.email }}
+
+
+ + {% if attestation.pdfFileSigned or attestation.pdfFileCertificate %} +
+ {% if attestation.pdfFileSigned %} + + Telecharger l'attestation signee + + {% endif %} + {% if attestation.pdfFileCertificate %} + + Telecharger l'audit de signature + + {% endif %} +
+ {% endif %} + +
+

Signature HMAC-SHA256

+

{{ attestation.hmac }}

+
+
+
+{% endblock %} diff --git a/templates/base.html.twig b/templates/base.html.twig index a9cdfb3..c4b7756 100644 --- a/templates/base.html.twig +++ b/templates/base.html.twig @@ -1,24 +1,193 @@ - - - - {% block title %}Welcome!{% endblock %} - - {% block stylesheets %} + + + + + {% block title %}CRM Ecosplay{% endblock %} + {{ pwa() }} + {% block meta %} + + {% endblock %} + + + {% if breadcrumbs is defined and breadcrumbs is not empty %} + + {% endif %} + {% block og %} + + + + + + + {% block og_image %} + {% endblock %} + + + + {% endblock %} + {% block stylesheets %}{% endblock %} + {% block javascripts %} + {{ vite_asset('app.js') }} + {% endblock %} + {% block head %}{% endblock %} + + + {% block header %} +
+ + + +
+ {% endblock %} + + {% block main %} +
{% block body %}{% endblock %} - +
+ {% endblock %} + + + + + diff --git a/templates/email/base.html.twig b/templates/email/base.html.twig new file mode 100644 index 0000000..0b91f5b --- /dev/null +++ b/templates/email/base.html.twig @@ -0,0 +1,36 @@ + + + + + + {% block title %}CRM Ecosplay{% endblock %} + + + + + + +
+

+ Cet email ne s'affiche pas correctement ? Voir en ligne +

+ + + + + + + + + + +
+ CRM Ecosplay +
+ {% block content %}{% endblock %} +
+ © {{ "now"|date("Y") }} Association E-Cosplay +
+
+ + diff --git a/templates/emails/2fa_code.html.twig b/templates/emails/2fa_code.html.twig new file mode 100644 index 0000000..f7734a2 --- /dev/null +++ b/templates/emails/2fa_code.html.twig @@ -0,0 +1,16 @@ +{% extends 'email/base.html.twig' %} + +{% block title %}Code de verification{% endblock %} + +{% block content %} +

Code de verification

+

Bonjour,

+

Voici votre code de verification pour vous connecter au CRM Ecosplay :

+ +
+ {{ code }} +
+ +

Ce code est valable quelques minutes. Si vous n'avez pas demande ce code, ignorez cet email.

+

Pour des raisons de securite, ne partagez jamais ce code avec qui que ce soit.

+{% endblock %} diff --git a/templates/emails/forgot_password_code.html.twig b/templates/emails/forgot_password_code.html.twig new file mode 100644 index 0000000..ca874be --- /dev/null +++ b/templates/emails/forgot_password_code.html.twig @@ -0,0 +1,14 @@ +{% extends 'email/base.html.twig' %} + +{% block title %}Code de reinitialisation{% endblock %} + +{% block content %} +

Reinitialisation du mot de passe

+

Bonjour,

+

Vous avez demande la reinitialisation de votre mot de passe sur le CRM Ecosplay. Voici votre code de verification :

+
+ {{ code }} +
+

Ce code est valable 10 minutes. Si vous n'avez pas demande cette reinitialisation, ignorez cet email.

+

Pour des raisons de securite, ne partagez jamais ce code avec qui que ce soit.

+{% endblock %} diff --git a/templates/emails/membre_created.html.twig b/templates/emails/membre_created.html.twig new file mode 100644 index 0000000..cd8363d --- /dev/null +++ b/templates/emails/membre_created.html.twig @@ -0,0 +1,59 @@ +{% extends 'email/base.html.twig' %} + +{% block title %}Votre compte CRM Ecosplay{% endblock %} + +{% block content %} +

Bienvenue {{ firstName }} !

+

Votre compte sur le CRM Ecosplay a ete cree par l'administration de l'association E-Cosplay.

+ +
+

Vos identifiants de connexion

+ + + + + + + + + +
Email{{ email }}
Mot de passe{{ tempPassword }}
+
+ +

Lors de votre premiere connexion, il vous sera demande de :

+
    +
  • Changer votre mot de passe
  • +
  • Configurer la double authentification (OTP) via une application telle que Google Authenticator, Authy ou Microsoft Authenticator
  • +
+ + {% if groups|length > 0 %} +

Acces attribues :

+
    + {% for group in groups %} +
  • + {% if group == 'gp_ticket' %}Billetterie + {% elseif group == 'gp_asso' %}Association + {% elseif group == 'gp_contest' %}Concours + {% elseif group == 'gp_mail' %}Mail + {% elseif group == 'gp_mailing' %}Mailing + {% elseif group == 'gp_sign' %}Signature electronique + {% elseif group == 'gp_ndd' %}Nom de domaine + {% else %}{{ group }} + {% endif %} +
  • + {% endfor %} +
+ {% endif %} + + + + + +
+ Se connecter +
+ +

Une fois sur la page de connexion, cliquez sur le bouton "Connexion E-Cosplay" puis saisissez vos identifiants ci-dessus.

+ +

Pour des raisons de securite, ne partagez jamais vos identifiants. Si vous n'etes pas a l'origine de cette demande, contactez contact@e-cosplay.fr.

+{% endblock %} diff --git a/templates/emails/password_changed.html.twig b/templates/emails/password_changed.html.twig new file mode 100644 index 0000000..71c3405 --- /dev/null +++ b/templates/emails/password_changed.html.twig @@ -0,0 +1,11 @@ +{% extends 'email/base.html.twig' %} + +{% block title %}Mot de passe modifie{% endblock %} + +{% block content %} +

Mot de passe modifie

+

Bonjour,

+

Votre mot de passe sur le CRM Ecosplay a ete modifie avec succes.

+

Si vous n'etes pas a l'origine de cette modification, contactez immediatement notre support a l'adresse contact@e-cosplay.fr.

+

Cordialement,
Association E-Cosplay

+{% endblock %} diff --git a/templates/emails/revendeur_created.html.twig b/templates/emails/revendeur_created.html.twig new file mode 100644 index 0000000..3a99d1c --- /dev/null +++ b/templates/emails/revendeur_created.html.twig @@ -0,0 +1,74 @@ +{% extends 'email/base.html.twig' %} + +{% block title %}Bienvenue chez E-Cosplay - Espace Revendeur{% endblock %} + +{% block content %} +

Bienvenue {{ firstName }} !

+

Nous avons le plaisir de vous informer que votre compte revendeur a ete cree sur le CRM Ecosplay.

+ +
+

Votre code revendeur

+

{{ codeRevendeur }}

+
+ +

Pour acceder a votre espace, suivez ces etapes :

+ + + + + + + + + + + + + +
+

Etape 1

+

Cliquez sur le bouton ci-dessous pour definir votre mot de passe. Ce lien est a usage unique.

+
+

Etape 2

+

Une fois votre mot de passe defini, rendez-vous sur la page de connexion et identifiez-vous avec votre email {{ email }} et votre nouveau mot de passe.

+
+

Etape 3

+

Vous accederez a votre Espace Revendeur ou vous pourrez gerer vos ventes, commandes et factures.

+
+ + + + + +
+ Definir mon mot de passe +
+ + + + + +
+ Acceder a la page de connexion +
+ +
+

Informations de votre compte :

+ + + + + + + + + + + + + +
Code revendeur{{ codeRevendeur }}
Email{{ email }}
Nom{{ firstName }} {{ lastName }}
+
+ +

Pour toute question, contactez-nous a contact@e-cosplay.fr. Conservez precieusement votre code revendeur {{ codeRevendeur }}.

+{% endblock %} diff --git a/templates/emails/rgpd_access.html.twig b/templates/emails/rgpd_access.html.twig new file mode 100644 index 0000000..d95c32f --- /dev/null +++ b/templates/emails/rgpd_access.html.twig @@ -0,0 +1,11 @@ +{% extends 'email/base.html.twig' %} + +{% block title %}RGPD - Vos donnees personnelles{% endblock %} + +{% block content %} +

Vos donnees personnelles

+

Bonjour,

+

Suite a votre demande d'acces (article 15 du RGPD), vous trouverez en piece jointe un document PDF contenant l'ensemble des donnees de navigation que nous detenons vous concernant.

+

Si vous souhaitez exercer votre droit a l'effacement, vous pouvez en faire la demande depuis la page Politique RGPD.

+

Cordialement,
Association E-Cosplay

+{% endblock %} diff --git a/templates/emails/rgpd_attestation_signed.html.twig b/templates/emails/rgpd_attestation_signed.html.twig new file mode 100644 index 0000000..910fae9 --- /dev/null +++ b/templates/emails/rgpd_attestation_signed.html.twig @@ -0,0 +1,25 @@ +{% extends 'email/base.html.twig' %} + +{% block title %}Attestation RGPD signee{% endblock %} + +{% block content %} +

Attestation RGPD signee

+

Bonjour,

+

Suite a votre demande, vous trouverez en piece jointe votre attestation d'{{ typeName }}, signee electroniquement par l'association E-Cosplay.

+ + + + + + + + + + + + + +
Reference{{ attestation.reference }}
Date{{ attestation.createdAt|date('d/m/Y a H:i') }}
Type{{ typeName|capitalize }}
+

Le certificat de signature electronique est egalement joint a cet email.

+

Cordialement,
Association E-Cosplay

+{% endblock %} diff --git a/templates/emails/rgpd_deletion.html.twig b/templates/emails/rgpd_deletion.html.twig new file mode 100644 index 0000000..1cc110c --- /dev/null +++ b/templates/emails/rgpd_deletion.html.twig @@ -0,0 +1,11 @@ +{% extends 'email/base.html.twig' %} + +{% block title %}RGPD - Attestation de suppression{% endblock %} + +{% block content %} +

Attestation de suppression

+

Bonjour,

+

Suite a votre demande d'effacement (article 17 du RGPD), nous confirmons que toutes vos donnees de navigation ont ete definitivement supprimees de nos systemes.

+

Vous trouverez en piece jointe une attestation de suppression au format PDF.

+

Cordialement,
Association E-Cosplay

+{% endblock %} diff --git a/templates/emails/rgpd_no_data.html.twig b/templates/emails/rgpd_no_data.html.twig new file mode 100644 index 0000000..72150aa --- /dev/null +++ b/templates/emails/rgpd_no_data.html.twig @@ -0,0 +1,12 @@ +{% extends 'email/base.html.twig' %} + +{% block title %}RGPD - Aucune donnee trouvee{% endblock %} + +{% block content %} +

Demande RGPD

+

Bonjour,

+

Nous avons bien recu votre demande concernant vos donnees personnelles.

+

Apres verification, aucune donnee n'a ete trouvee correspondant a l'adresse IP indiquee dans nos systemes.

+

Si vous pensez qu'il s'agit d'une erreur, n'hesitez pas a nous contacter a contact@e-cosplay.fr.

+

Cordialement,
Association E-Cosplay

+{% endblock %} diff --git a/templates/espace_client/index.html.twig b/templates/espace_client/index.html.twig new file mode 100644 index 0000000..97e3b53 --- /dev/null +++ b/templates/espace_client/index.html.twig @@ -0,0 +1,17 @@ +{% extends 'base.html.twig' %} + +{% block title %}Espace Client - CRM Ecosplay{% endblock %} +{% block description %}Espace client de la plateforme CRM Ecosplay.{% endblock %} + +{% block body %} +
+

Espace Client

+ +
+
+

Bienvenue dans votre espace

+

Gerez vos informations, suivez vos commandes et consultez vos documents depuis cet espace.

+
+
+
+{% endblock %} diff --git a/templates/espace_prestataire/index.html.twig b/templates/espace_prestataire/index.html.twig new file mode 100644 index 0000000..07f2c7e --- /dev/null +++ b/templates/espace_prestataire/index.html.twig @@ -0,0 +1,17 @@ +{% extends 'base.html.twig' %} + +{% block title %}Espace Prestataire - CRM Ecosplay{% endblock %} +{% block description %}Espace prestataire de la plateforme CRM Ecosplay.{% endblock %} + +{% block body %} +
+

Espace Prestataire

+ +
+
+

Bienvenue dans votre espace

+

Gerez vos prestations, consultez vos factures et suivez vos missions depuis cet espace.

+
+
+
+{% endblock %} diff --git a/templates/external_redirect.html.twig b/templates/external_redirect.html.twig new file mode 100644 index 0000000..0574370 --- /dev/null +++ b/templates/external_redirect.html.twig @@ -0,0 +1,47 @@ +{% extends 'base.html.twig' %} + +{% block title %}Redirection externe - CRM Ecosplay{% endblock %} + +{% block header %} +
+
+ + CRM Ecosplay + +
+
+{% endblock %} + +{% block body %} +
+
+
+
+ +
+

Redirection externe

+

Vous etes sur le point de quitter CRM Ecosplay pour un site externe.

+ + {% if url %} +
+

{{ url }}

+
+ + + {% else %} +

Aucune URL de redirection fournie.

+ + Retour a l'accueil + + {% endif %} +
+
+
+{% endblock %} diff --git a/templates/home/index.html.twig b/templates/home/index.html.twig new file mode 100644 index 0000000..563e4ca --- /dev/null +++ b/templates/home/index.html.twig @@ -0,0 +1,68 @@ +{% extends 'base.html.twig' %} + +{% block title %}CRM Ecosplay{% endblock %} +{% block description %}Connectez-vous au CRM Ecosplay pour acceder a votre espace client ou revendeur.{% endblock %} + +{% block header %} +
+
+ + CRM Ecosplay + +
+
+{% endblock %} + +{% block body %} + {% if app.user %} + {# Tableau de bord dynamique selon le role #} +
+

Tableau de bord

+

Bienvenue, {{ app.user.fullName }}.

+
+ {% else %} +
+
+
+
+

CRM Ecosplay

+

Espace de gestion

+
+
+
+
+ + +
+ +
+ + +
+ + + + + Mot de passe oublie ? +
+ + +
+
+
+
+ {% endif %} +{% endblock %} diff --git a/templates/legal/_layout.html.twig b/templates/legal/_layout.html.twig new file mode 100644 index 0000000..0fb5af5 --- /dev/null +++ b/templates/legal/_layout.html.twig @@ -0,0 +1,11 @@ +{% extends 'base.html.twig' %} + +{% block header %} +
+
+ + CRM Ecosplay + +
+
+{% endblock %} diff --git a/templates/legal/cgu.html.twig b/templates/legal/cgu.html.twig new file mode 100644 index 0000000..5045c79 --- /dev/null +++ b/templates/legal/cgu.html.twig @@ -0,0 +1,234 @@ +{% extends 'legal/_layout.html.twig' %} + +{% block title %}Conditions Generales d'Utilisation - CRM Ecosplay{% endblock %} +{% block description %}Conditions generales d'utilisation du CRM Ecosplay.{% endblock %} + +{% block body %} +
+

Conditions Generales d'Utilisation

+ +
+ +
+

Article 1 - Objet

+

Les presentes Conditions Generales d'Utilisation (ci-apres "CGU") ont pour objet de definir les modalites et conditions d'acces et d'utilisation de la plateforme CRM Ecosplay, accessible a l'adresse crm.e-cosplay.fr, editee par l'association E-Cosplay.

+

Le CRM Ecosplay est une plateforme de gestion de la relation commerciale et administrative entre l'association E-Cosplay, ses clients et ses prestataires (revendeurs). L'acces a la Plateforme implique l'acceptation pleine et entiere des presentes CGU.

+
+ +
+

Article 2 - Editeur

+
    +
  • Association E-Cosplay
  • +
  • RNA : W022006988 / SIREN : 943121517
  • +
  • Siege social : 42 rue de Saint-Quentin, 02800 Beautor, France
  • +
  • Email : contact@e-cosplay.fr
  • +
  • Site web : www.e-cosplay.fr
  • +
+
+ +
+

Article 3 - Description des services

+

La Plateforme propose des services differencies selon le profil de l'utilisateur :

+ +

Espace Client :

+
    +
  • Consultation et telechargement des factures
  • +
  • Suivi et historique des paiements
  • +
  • Commande de produits et services
  • +
  • Acces au support client et suivi des demandes
  • +
  • Gestion des informations personnelles
  • +
+ +

Espace Prestataire (revendeur) :

+
    +
  • Gestion des ventes et du chiffre d'affaires
  • +
  • Suivi des commandes clients
  • +
  • Consultation et telechargement des factures
  • +
  • Gestion du catalogue produits
  • +
  • Tableaux de bord et statistiques de vente
  • +
+ +

L'utilisation interne de la Plateforme par l'association E-Cosplay et ses membres reste strictement confidentielle et n'est pas couverte par les presentes CGU.

+
+ +
+

Article 4 - Acces a la Plateforme

+

L'acces au CRM Ecosplay est reserve aux personnes disposant d'un compte utilisateur valide, delivre par l'association E-Cosplay. L'acces peut etre accorde aux categories suivantes :

+
    +
  • Clients : personnes physiques ou morales beneficiant des produits et services de l'association
  • +
  • Prestataires (revendeurs) : personnes physiques ou morales commercialisant les produits et services de l'association
  • +
+

L'association se reserve le droit de suspendre ou supprimer tout compte, sans preavis, en cas de non-respect des presentes CGU.

+
+ +
+

Article 5 - Creation de compte et authentification

+

Aucune inscription en libre-service n'est disponible sur la Plateforme. La creation d'un compte utilisateur est exclusivement a l'initiative de l'association E-Cosplay. Un compte peut etre cree :

+
    +
  • Suite a une commande de produits ou services aupres de l'association
  • +
  • Suite a la conclusion d'un contrat de partenariat (prestataire/revendeur)
  • +
  • Sur demande validee par un membre de l'association
  • +
+

L'authentification pour les clients et prestataires s'effectue via :

+
    +
  • Une adresse email et un mot de passe
  • +
  • Une double authentification (2FA) obligatoire, via un code envoye par email ou une application d'authentification (TOTP)
  • +
+

Les methodes d'authentification et de creation de compte utilisees par les membres de l'association restent strictement confidentielles.

+

L'utilisateur s'engage a :

+
    +
  • Ne pas communiquer ses identifiants de connexion a des tiers
  • +
  • Activer et maintenir la double authentification sur son compte
  • +
  • Informer immediatement l'association en cas de suspicion d'utilisation non autorisee de son compte
  • +
+

L'utilisateur est seul responsable de l'utilisation faite de son compte et de la confidentialite de ses identifiants.

+
+ +
+

Article 6 - Utilisation de la Plateforme

+

L'utilisateur s'engage a utiliser la Plateforme de maniere loyale et conformement a sa destination. Il lui est notamment interdit de :

+
    +
  • Utiliser la Plateforme a des fins illicites, frauduleuses ou portant atteinte aux droits de tiers
  • +
  • Tenter d'acceder a des donnees ou fonctionnalites auxquelles il n'a pas ete autorise
  • +
  • Introduire des virus, malwares ou tout code malveillant
  • +
  • Collecter ou extraire des donnees de la Plateforme de maniere automatisee (scraping, crawling)
  • +
  • Perturber le fonctionnement normal de la Plateforme
  • +
  • Usurper l'identite d'un autre utilisateur
  • +
  • Divulguer des informations commerciales confidentielles obtenues via la Plateforme
  • +
+
+ +
+

Article 7 - Commandes et paiements

+

Pour les clients :

+
    +
  • Les commandes de produits et services sont effectuees via la Plateforme et sont soumises aux Conditions Generales de Vente
  • +
  • Les paiements sont securises par Stripe (Stripe Payments Europe, Ltd., Dublin, Irlande). La Plateforme ne stocke aucune donnee bancaire
  • +
  • Les factures sont mises a disposition dans l'Espace Client et peuvent etre telechargees au format PDF
  • +
+

Pour les prestataires (revendeurs) :

+
    +
  • Les prestataires disposent d'un acces a leurs ventes, commandes et factures via l'Espace Prestataire
  • +
  • Les conditions de remuneration et de commission sont definies dans le contrat de partenariat conclu entre le prestataire et l'association
  • +
+
+ +
+

Article 8 - Confidentialite

+

L'utilisateur reconnait que les informations accessibles via la Plateforme (donnees commerciales, tarifs, conditions prestataires, statistiques) sont strictement confidentielles.

+

L'utilisateur s'engage a :

+
    +
  • Ne pas divulguer ces informations a des tiers non autorises
  • +
  • Ne pas utiliser ces informations a des fins concurrentielles
  • +
  • Prendre toutes les mesures necessaires pour proteger la confidentialite de ces informations
  • +
+

Cette obligation de confidentialite survit a la cessation de l'utilisation de la Plateforme.

+
+ +
+

Article 9 - Donnees personnelles

+

La Plateforme collecte et traite des donnees personnelles conformement au Reglement General sur la Protection des Donnees (RGPD). Pour plus d'informations, consultez notre Politique de confidentialite.

+

L'utilisateur dispose d'un droit d'acces, de rectification, d'effacement et de portabilite de ses donnees. Ces droits peuvent etre exerces depuis la page Exercer vos droits ou en contactant le DPO a l'adresse contact@e-cosplay.fr.

+
+ +
+

Article 10 - Propriete intellectuelle

+

L'ensemble des elements composant la Plateforme (logiciels, textes, images, graphismes, logos, icones, code source, base de donnees) est la propriete exclusive de l'association E-Cosplay ou de ses partenaires, et est protege par les lois francaises et internationales relatives a la propriete intellectuelle.

+

Toute reproduction, representation, modification, distribution ou exploitation, meme partielle, de la Plateforme ou de son contenu, sans autorisation ecrite prealable, est strictement interdite.

+
+ +
+

Article 11 - Signature electronique

+

La Plateforme integre un service de signature electronique fourni par DocuSeal, heberge sur l'infrastructure de l'association (signature.esy-web.dev). Ce service est utilise pour la signature des attestations, contrats et documents officiels.

+

En utilisant ce service, l'utilisateur reconnait que :

+
    +
  • La signature electronique a la meme valeur juridique qu'une signature manuscrite conformement au reglement eIDAS et au Code civil francais (article 1367)
  • +
  • Un certificat de signature electronique et un journal d'audit sont generes pour chaque document signe
  • +
  • Les documents signes sont verifiables en ligne via un QR code et un lien de verification unique
  • +
+

Duree de conservation des documents signes :

+
    +
  • Attestations RGPD (consultation et suppression) : 7 jours apres signature, puis suppression automatique de la Plateforme et du serveur de signature
  • +
  • Contrats : conserves pendant toute la duree du contrat + 3 mois apres la fin (en cas de retour ou reclamation du client)
  • +
  • Attestation de fin de contrat : conservee pendant 1 an apres sa delivrance
  • +
  • Factures et documents comptables : conserves pendant 10 ans conformement aux obligations legales (article L123-22 du Code de commerce)
  • +
  • Attestation de solde restant a payer : valable 1 mois a compter de sa date d'emission
  • +
  • Attestation de conformite (en regle) : valable 1 mois a compter de sa date d'emission
  • +
+

Au-dela de ces delais, les documents sont automatiquement supprimes de la Plateforme et du serveur de signature. L'utilisateur est invite a telecharger et conserver une copie de ses documents signes avant leur expiration.

+
+ +
+

Article 12 - Disponibilite

+

L'association E-Cosplay s'efforce d'assurer la disponibilite de la Plateforme 24h/24 et 7j/7. Toutefois, l'acces peut etre temporairement interrompu pour des raisons de maintenance, de mise a jour ou en cas de force majeure.

+

L'association ne saurait etre tenue responsable des interruptions de service et de leurs consequences eventuelles.

+
+ +
+

Article 13 - Responsabilites

+

Responsabilite de l'association :

+
    +
  • L'association met en oeuvre les moyens necessaires pour assurer la securite et la confidentialite des donnees
  • +
  • L'association ne garantit pas l'absence totale d'erreurs ou de failles de securite
  • +
  • L'association ne saurait etre tenue responsable des dommages indirects resultant de l'utilisation de la Plateforme
  • +
+

Responsabilite de l'utilisateur :

+
    +
  • L'utilisateur est responsable de l'exactitude des informations qu'il fournit
  • +
  • L'utilisateur est responsable de la securite de ses identifiants de connexion
  • +
  • L'utilisateur s'engage a respecter les presentes CGU, les obligations de confidentialite et la legislation en vigueur
  • +
+
+ +
+

Article 14 - Cookies

+

La Plateforme utilise des cookies strictement necessaires a son fonctionnement. Pour plus de details, consultez notre Politique de cookies.

+
+ +
+

Article 15 - Modification des CGU

+

L'association E-Cosplay se reserve le droit de modifier les presentes CGU a tout moment. Les utilisateurs seront informes de toute modification substantielle par email ou par notification sur la Plateforme.

+

La poursuite de l'utilisation de la Plateforme apres modification des CGU vaut acceptation des nouvelles conditions.

+
+ +
+

Article 16 - Resiliation et cloture de compte

+

A l'initiative de l'utilisateur :

+

L'utilisateur peut demander la cloture de son compte en contactant l'association a l'adresse contact@e-cosplay.fr. La cloture du compte est soumise aux conditions suivantes :

+
    +
  • Le compte ne peut etre cloture que si l'utilisateur est en regle vis-a-vis de l'association (aucun impaye, aucune facture en cours, aucune obligation contractuelle en suspens)
  • +
  • En cas d'impaye ou de litige en cours, la demande de cloture sera refusee jusqu'a la regularisation complete de la situation
  • +
  • Une attestation de conformite pourra etre demandee prealablement a la cloture
  • +
+

Une fois la cloture effective, une attestation de fin de contrat sera delivree a l'utilisateur, confirmant la resiliation de son acces a la Plateforme.

+

A l'initiative de l'association :

+

L'association se reserve le droit de suspendre ou de resilier l'acces d'un utilisateur dans les cas suivants :

+
    +
  • Manquement aux presentes CGU
  • +
  • Non-paiement des sommes dues apres mise en demeure restee infructueuse
  • +
  • Utilisation frauduleuse ou abusive de la Plateforme
  • +
  • Atteinte a la confidentialite des informations (article 8)
  • +
+

La suspension ou la resiliation peut intervenir sans preavis ni indemnite en cas de manquement grave. L'obligation de confidentialite prevue a l'article 8 reste en vigueur apres la resiliation.

+
+ +
+

Article 17 - Droit applicable et litiges

+

Les presentes CGU sont regies par le droit francais. En cas de litige relatif a l'interpretation ou a l'execution des presentes CGU, les parties s'efforceront de trouver une solution amiable.

+

A defaut d'accord amiable, les tribunaux competents de Laon (Aisne) seront seuls competents.

+
+ +
+

Article 18 - Contact

+

Pour toute question relative aux presentes CGU :

+
    +
  • Email : contact@e-cosplay.fr
  • +
  • Telephone : 06 79 34 88 02
  • +
  • Courrier : Association E-Cosplay, 42 rue de Saint-Quentin, 02800 Beautor, France
  • +
+
+ +

Derniere mise a jour : 31/03/2026

+
+
+{% endblock %} diff --git a/templates/legal/cgv.html.twig b/templates/legal/cgv.html.twig new file mode 100644 index 0000000..51221dd --- /dev/null +++ b/templates/legal/cgv.html.twig @@ -0,0 +1,267 @@ +{% extends 'legal/_layout.html.twig' %} + +{% block title %}Conditions Generales de Vente - CRM Ecosplay{% endblock %} +{% block description %}Conditions generales de vente du CRM Ecosplay.{% endblock %} + +{% block body %} +
+

Conditions Generales de Vente

+ +
+ +
+

Article 1 - Objet

+

Les presentes Conditions Generales de Vente (ci-apres "CGV") s'appliquent a l'ensemble des produits et services proposes par l'association E-Cosplay, sauf mention speciale contraire precisee dans un contrat ou un document particulier.

+

La validation des presentes CGV par le client reste valable pendant toute la duree du contrat en cours, sauf cas particulier expressement convenu entre les parties.

+

Toute commande ou souscription a un service implique l'acceptation pleine et entiere des presentes CGV.

+
+ +
+

Article 2 - Prestations

+

L'association E-Cosplay propose les prestations suivantes, accessibles via la plateforme crm.e-cosplay.fr :

+
    +
  • Service de Site Internet : creation, hebergement et maintenance de sites internet via le CMS Esy-Web, solution developpee par l'association
  • +
  • Service de Billetterie en ligne : creation, gestion et vente de billets en ligne via la plateforme ticket.e-cosplay.fr
  • +
  • Service aux Associations : gestion des adherents, comptabilite associative et outils de gestion via la plateforme asso.e-cosplay.fr
  • +
  • Service de Mail : hebergement et gestion de messagerie professionnelle via mail.e-cosplay.fr, solution developpee par la SARL SITECONSEIL, utilisant l'infrastructure Amazon Simple Email Service (AWS SES)
  • +
  • Service de Mailing : creation et envoi de campagnes emailing via la plateforme mailing.e-cosplay.fr
  • +
  • Service de Signature Electronique : signature electronique de documents via la plateforme signature.e-cosplay.fr, propulsee par DocuSeal
  • +
  • Service de Nom de Domaine : depot, enregistrement, renouvellement et gestion de noms de domaine pour le compte du client
  • +
+

Le detail des prestations, leurs specificites techniques et les options disponibles sont precises dans le contrat de service conclu entre le client et l'association.

+

Cette liste n'est pas exhaustive. De nouveaux services pourront etre proposes a l'avenir. L'association se reserve le droit de faire evoluer son offre de prestations. Les clients seront informes de toute modification impactant leur contrat en cours.

+
+ +
+

Article 3 - Disponibilite des services

+

L'association E-Cosplay s'efforce d'assurer la disponibilite de ses services 24h/24 et 7j/7. Toutefois, l'acces aux services peut etre temporairement interrompu pour des raisons de :

+
    +
  • Maintenance programmee ou d'urgence
  • +
  • Mise a jour des infrastructures ou des logiciels
  • +
  • Incident technique ou panne chez un prestataire tiers (hebergeur, fournisseur d'acces, etc.)
  • +
  • Force majeure
  • +
+

L'association met en oeuvre les moyens raisonnables pour limiter la duree et la frequence des interruptions. En cas de maintenance programmee, les clients seront informes dans la mesure du possible avec un preavis raisonnable.

+

L'association ne saurait etre tenue responsable des dommages directs ou indirects resultant d'une interruption de service, quelle qu'en soit la cause.

+
+ +
+

Article 4 - Tarifs

+

Tous les tarifs sont exprimes en euros hors taxes (€ HT). Les tarifs des prestations proposees par l'association E-Cosplay sont evolutifs et non fixes. Ils peuvent etre modifies a tout moment en fonction de l'evolution des couts, des services et du marche.

+

L'ensemble des tarifs en vigueur est consultable sur la page Tarifs.

+

En cas de modification tarifaire, les clients disposant d'un contrat en cours ou utilisant un service concerne seront avertis au minimum 1 mois avant l'entree en vigueur du nouveau tarif, par email ou par notification sur la Plateforme.

+

Lors de la signature d'un contrat, le tarif en vigueur est garanti pendant une duree de 6 mois a compter de la date de signature. Aucune modification tarifaire ne pourra etre appliquee durant cette periode.

+

A l'issue de ce delai de preavis, le client pourra :

+
    +
  • Accepter le nouveau tarif et poursuivre l'utilisation du service
  • +
  • Resilier son contrat sans frais ni penalite avant l'application du nouveau tarif, conformement aux conditions de resiliation prevues a l'article 14
  • +
+
+ +
+

Article 5 - Paiement

+

L'ensemble des paiements est traite de maniere securisee par Stripe (Stripe Payments Europe, Ltd., 1 Grand Canal Street Lower, Dublin 2, Irlande). La Plateforme ne stocke aucune donnee bancaire.

+

Les moyens de paiement acceptes sont exclusivement ceux proposes par Stripe, notamment :

+
    +
  • Carte bancaire (Visa, Mastercard, American Express, etc.)
  • +
  • Virement bancaire SEPA
  • +
  • Prelevement SEPA
  • +
  • Tout autre moyen de paiement disponible via Stripe
  • +
+

Aucun paiement en especes, par cheque ou par tout autre moyen hors Stripe n'est accepte, sauf accord ecrit prealable de l'association.

+

Les factures sont payables a reception, sauf conditions particulieres precisees dans le contrat de service. En cas de souscription a un abonnement, le paiement est preleve automatiquement selon la periodicite convenue (mensuelle, trimestrielle ou annuelle).

+
+ +
+

Article 6 - Validation des commandes et verification

+

Toute commande ou souscription a un service fait l'objet de verifications prealables par l'association E-Cosplay avant acceptation. Ces verifications peuvent inclure :

+
    +
  • Verification de l'entreprise : controle du numero SIRET/SIREN aupres des registres officiels de l'Etat (INSEE, Infogreffe) afin de s'assurer que l'entreprise est en activite et non suspendue, radiee ou en liquidation
  • +
  • Verification d'identite : controle de l'identite du representant legal ou du contact principal, si requis par la nature de la prestation
  • +
  • Verification de solvabilite : analyse de la solvabilite du client, si requis par le montant ou la duree de l'engagement
  • +
+

L'association se reserve le droit de refuser toute commande, sans avoir a justifier sa decision, notamment dans les cas suivants :

+
    +
  • Informations fournies inexactes ou incompletes
  • +
  • Entreprise non immatriculee, radiee, suspendue ou en liquidation
  • +
  • Solvabilite insuffisante
  • +
  • Antecedents d'impayes aupres de l'association
  • +
  • Activite du client incompatible avec les valeurs ou l'objet de l'association
  • +
+

En cas de refus, le client sera informe par email. Aucun paiement ne sera preleve tant que la commande n'a pas ete validee par l'association.

+
+ +
+

Article 7 - Processus de facturation et relances

+

Le cycle de facturation et de relance suit le processus suivant :

+
    +
  • J-15 (Avis de paiement) : un avis de paiement est envoye par email 15 jours avant la date d'echeance. Si vous etes en prelevement automatique, aucune action n'est requise. Dans le cas contraire, le lien de paiement securise (Stripe) est inclus dans l'email
  • +
  • J-7 (Rappel) : en l'absence de paiement, un email de rappel est envoye 7 jours avant l'echeance
  • +
  • J-3 (Avertissement) : un dernier avertissement est envoye, informant le client que ses services seront suspendus a defaut de regularisation avant la date d'echeance
  • +
  • J (Echeance - Suspension) : en cas de non-paiement a la date d'echeance, les services sont immediatement suspendus et une mise en demeure est adressee au client par email
  • +
  • J+7 (Resiliation et recouvrement) : sans regularisation dans les 7 jours suivant la suspension, le contrat est resilie de plein droit et la creance est transmise en recouvrement
  • +
+

Pendant la periode de suspension (J a J+7), le client peut regulariser sa situation a tout moment en procedant au paiement. Les services seront alors retablis dans un delai de 24 heures.

+

En cas de prelevement automatique :

+

Les etapes J-7 et J-3 ne s'appliquent pas. Si le prelevement est refuse ou echoue au moment de l'echeance, le processus suivant s'applique :

+
    +
  • J (Echec du prelevement) : un email est envoye au client pour l'informer du rejet et l'inviter a regulariser sa situation sous 7 jours via le lien de paiement
  • +
  • J+8 (Mise en demeure et suspension) : sans regularisation, les services sont suspendus et une mise en demeure est adressee au client
  • +
  • J+15 (Resiliation et recouvrement) : sans regularisation, le contrat est resilie de plein droit et la creance est transmise en recouvrement
  • +
+

L'association se reserve le droit d'appliquer des penalites de retard conformement a l'article 9 (Impayes).

+
+ +
+

Article 8 - Droit de retractation

+

Conformement a l'article L221-28 du Code de la consommation, le droit de retractation ne s'applique pas aux prestations proposees par l'association E-Cosplay, celles-ci constituant des services numeriques fournis de maniere immediate apres la validation de la commande.

+

En acceptant les presentes CGV et en validant sa commande, le client reconnait et accepte expressement que :

+
    +
  • L'execution du service commence immediatement apres la confirmation de la commande
  • +
  • Il renonce expressement a son droit de retractation conformement aux dispositions legales en vigueur
  • +
+

Cette renonciation est confirmee par le client lors de la signature du contrat de service.

+
+ +
+

Article 9 - Impayes

+

En cas de non-paiement a la date d'echeance :

+
    +
  • Regularisation sous 3 jours : aucune penalite de retard ne sera appliquee. Le client beneficie d'un delai de grace de 3 jours a compter de la date d'echeance pour regulariser sa situation sans frais supplementaires
  • +
  • Au-dela de 3 jours : une penalite forfaitaire de 40 € par tranche de 7 jours de retard sera appliquee, conformement aux articles L441-10 et D441-5 du Code de commerce
  • +
+

Exemple :

+
    +
  • Retard de 4 a 10 jours : 40 €
  • +
  • Retard de 11 a 17 jours : 80 €
  • +
  • Retard de 18 a 24 jours : 120 €
  • +
+

En cas de suspension des services suite a un impaye, des frais de remise en service de 50 € seront appliques a compter du 3e jour suivant la suspension. Ces frais s'ajoutent aux penalites de retard et au montant de la facture impayee.

+

Si le client dispose de plusieurs services (ex. : site internet, email, billetterie), seul le service concerne par l'impaye sera suspendu. Les autres services restent actifs et ne sont pas impactes, sauf en cas d'impayes multiples.

+

Les penalites de retard et frais de remise en service sont exigibles de plein droit, sans qu'un rappel soit necessaire. Ils sont factures automatiquement par la Plateforme.

+
+ +
+

Article 10 - Recouvrement

+

En cas d'impaye sans aucune action de regularisation de la part du client, la creance sera transmise a une societe de recouvrement dans un delai de 30 jours a compter de la date d'echeance initiale.

+

Les frais de recouvrement engages (frais de dossier, honoraires de la societe de recouvrement, frais de justice le cas echeant) seront integralement a la charge du client debiteur, en plus du montant de la creance, des penalites de retard et des frais de remise en service.

+

Avant toute transmission en recouvrement, l'association aura prealablement :

+
    +
  • Envoye les relances prevues a l'article 7 (processus de facturation)
  • +
  • Adresse une mise en demeure par email
  • +
  • Suspendu les services concernes
  • +
  • Resilie le contrat conformement a l'article 14
  • +
+

Le client reste libre de regulariser sa situation a tout moment avant la transmission en recouvrement en contactant l'association a l'adresse contact@e-cosplay.fr.

+
+ +
+

Article 11 - Facilites de paiement

+

Des facilites de paiement peuvent etre proposees au client via les services tiers integres a Stripe, sous reserve de leur disponibilite :

+
    +
  • PayPal : paiement fractionne ou differe selon les conditions de PayPal
  • +
  • Klarna : paiement en plusieurs fois ou differe selon les conditions de Klarna
  • +
  • Alma : paiement en 2, 3 ou 4 fois selon les conditions d'Alma
  • +
+

Ces facilites de paiement sont soumises a la validation des services tiers concernes (PayPal, Klarna, Alma). L'acceptation ou le refus d'une demande de paiement fractionne releve exclusivement de la decision de ces prestataires.

+

L'association E-Cosplay ne peut en aucun cas etre tenue responsable en cas de refus d'une facilite de paiement par l'un de ces services. Seuls les prestataires tiers determinent les conditions d'eligibilite et d'acceptation.

+

En cas de refus, le client devra proceder au paiement integral par l'un des autres moyens de paiement acceptes (article 5).

+

Echeancier de paiement en cas d'impaye :

+

En cas d'impaye, l'association pourra, a sa seule discretion et en fonction du profil et de l'historique du client, proposer un echeancier de paiement. Cet echeancier est soumis aux conditions suivantes :

+
    +
  • Une majoration de 5 % du montant total de la creance sera appliquee au titre des frais de gestion de l'echeancier
  • +
  • L'echeancier devra etre accepte et signe par le client
  • +
  • En cas de non-respect d'une seule echeance, l'integralite du solde restant du devient immediatement exigible et la procedure de recouvrement (article 10) sera engagee
  • +
+

L'octroi d'un echeancier n'est pas un droit. L'association se reserve le droit de refuser toute demande d'echeancier sans avoir a motiver sa decision.

+
+ +
+

Article 12 - Signature du contrat

+

La signature de tout contrat de service s'effectue electroniquement via la plateforme de signature DocuSeal (signature.e-cosplay.fr).

+

Une fois signe electroniquement par le client, le contrat est repute accepte par celui-ci. Toutefois, le contrat n'entre en vigueur qu'apres validation expresse de l'association E-Cosplay.

+

La validation par l'association intervient dans un delai de 2 a 3 jours ouvres apres la signature, apres les verifications prealables prevues a l'article 6 (validation des commandes et verification). Le client sera informe par email de la validation ou du refus de son contrat.

+

Pendant ce delai de validation, le client conserve la possibilite d'annuler sa demande :

+
    +
  • Directement depuis son Espace Client sur la Plateforme, si son compte est deja actif
  • +
  • Par simple email a l'adresse contact@e-cosplay.fr
  • +
+

Une fois le contrat valide par l'association, l'annulation n'est plus possible et les conditions de resiliation prevues a l'article 14 s'appliquent.

+

La signature electronique a la meme valeur juridique qu'une signature manuscrite, conformement au reglement europeen eIDAS et a l'article 1367 du Code civil francais. Un certificat de signature et un journal d'audit sont generes pour chaque contrat signe.

+
+ +
+

Article 13 - Engagement

+

Les prestations proposees par l'association E-Cosplay sont sans engagement de duree. Le client peut resilier a tout moment conformement aux conditions prevues a l'article 14.

+

Toutefois, certains services necessitent un paiement anticipe selon une periodicite definie lors de la souscription :

+
    +
  • Mensuel : paiement tous les 1 mois
  • +
  • Trimestriel : paiement tous les 3 mois
  • +
  • Semestriel : paiement tous les 6 mois
  • +
  • Annuel : paiement tous les 12 mois
  • +
+

La periodicite de paiement est precisee dans le contrat de service. En cas de resiliation en cours de periode, les sommes deja versees ne sont pas remboursables. Le service reste actif jusqu'a la fin de la periode payee.

+

La periodicite de paiement applicable a chaque service est consultable sur la page Tarifs.

+
+ +
+

Article 14 - Resiliation du contrat

+

Le client peut resilier son contrat a tout moment, pour n'importe lequel des services souscrits, directement depuis son Espace Client sur la Plateforme.

+

La resiliation prend effet avec un preavis de 1 mois a compter de la date de la demande. Le contrat prend fin au dernier jour du mois suivant la demande de resiliation, avec l'emission d'une derniere facture couvrant la periode restante. Pendant ce delai, le service reste accessible et les obligations contractuelles demeurent en vigueur.

+

Conditions de resiliation :

+
    +
  • Le client doit etre a jour de l'ensemble de ses paiements (aucun impaye en cours)
  • +
  • Les sommes deja facturees ou echues restent dues, y compris pour la periode de preavis
  • +
  • En cas d'engagement en cours (article 11), la resiliation anticipee peut entrainer l'application des conditions prevues audit article
  • +
+

A l'issue du preavis, une attestation de fin de contrat est delivree au client, confirmant la resiliation effective du service.

+

Resiliation a l'initiative de l'association :

+

L'association se reserve le droit de resilier un contrat en cas de manquement grave du client a ses obligations (non-paiement, utilisation frauduleuse, violation des CGU ou CGV), apres mise en demeure restee sans effet pendant 15 jours.

+
+ +
+

Article 15 - Cessation d'activite de l'association

+

En cas de dissolution ou de cessation d'activite de l'association E-Cosplay, les clients seront informes dans un delai minimum de 3 mois avant la date effective de fermeture, afin de leur permettre de preparer le transfert de leurs services vers un autre prestataire.

+

Pendant ce delai, l'association s'engage a :

+
    +
  • Maintenir les services actifs jusqu'a la date de fermeture effective
  • +
  • Fournir au client les elements necessaires a la migration de ses donnees et services (export des donnees, transfert de nom de domaine, etc.)
  • +
  • Ne facturer aucun service au-dela de la date de fermeture annoncee
  • +
+

Les contrats en cours dont la date de fin est posterieure a la date de fermeture seront resilies de plein droit a la date effective de cessation d'activite. Aucune somme deja versee ne sera remboursable, sauf accord prealable ecrit de l'association.

+

L'association n'est pas tenue de divulguer les raisons de la cessation d'activite, qu'elles soient liees a l'arret de l'association ou a l'arret d'une prestation specifique.

+
+ +
+

Article 16 - Resolution amiable des litiges

+

En cas de litige relatif a l'execution des presentes CGV, le client peut a tout moment contacter l'association E-Cosplay afin de rechercher une resolution amiable par le dialogue.

+

Le client peut adresser sa reclamation :

+
    +
  • Par email : contact@e-cosplay.fr
  • +
  • Par courrier : Association E-Cosplay, 42 rue de Saint-Quentin, 02800 Beautor, France
  • +
  • Via le support client accessible depuis l'Espace Client sur la Plateforme
  • +
+

L'association s'engage a accuser reception de la reclamation dans un delai de 5 jours ouvres et a proposer une solution dans un delai raisonnable.

+

Les parties s'engagent a privilegier le reglement amiable avant tout recours judiciaire.

+
+ +
+

Article 17 - Tribunal competent

+

Les presentes CGV sont regies par le droit francais.

+

En cas de litige non resolu par voie amiable (article 16) ou pour tout autre differend relatif a l'interpretation, l'execution ou la resiliation des presentes CGV, les tribunaux competents de Laon ou de Saint-Quentin (Aisne) seront seuls competents, quel que soit le lieu de domicile du client.

+
+ +
+

Article 18 - Contact

+

Pour toute question relative aux presentes CGV :

+
    +
  • Email : contact@e-cosplay.fr
  • +
  • Telephone : 06 79 34 88 02
  • +
  • Courrier : Association E-Cosplay, 42 rue de Saint-Quentin, 02800 Beautor, France
  • +
+
+ +

Derniere mise a jour : 31/03/2026

+
+
+{% endblock %} diff --git a/templates/legal/conformite.html.twig b/templates/legal/conformite.html.twig new file mode 100644 index 0000000..313a013 --- /dev/null +++ b/templates/legal/conformite.html.twig @@ -0,0 +1,148 @@ +{% extends 'legal/_layout.html.twig' %} + +{% block title %}Conformite - CRM Ecosplay{% endblock %} +{% block description %}Informations de conformite reglementaire et transparence de l'association E-Cosplay.{% endblock %} + +{% block body %} +
+

Conformite

+ +
+ +
+

1. Statut juridique

+

L'association E-Cosplay est une association a but non lucratif, regie par la loi du 1er juillet 1901 et le decret du 16 aout 1901.

+
    +
  • RNA : W022006988
  • +
  • SIREN : 943121517
  • +
  • Siege social : 42 rue de Saint-Quentin, 02800 Beautor, France
  • +
  • President : Jovann Serreau — jovann@e-cosplay.fr
  • +
+

L'association est declaree aupres de la Prefecture de l'Aisne et publiee au Journal Officiel des Associations et Fondations d'Entreprise (JOAFE).

+

+ + Consulter la publication au Journal Officiel (PDF) + + + + Avis de situation INSEE (PDF) +

+
+ +
+

2. Respect de la legislation associative

+

L'association E-Cosplay s'engage a respecter l'ensemble de la legislation applicable aux associations a but non lucratif, notamment :

+
    +
  • Loi du 1er juillet 1901 : statuts deposes, assemblee generale annuelle, tenue d'une comptabilite reguliere
  • +
  • Code general des impots : respect des obligations fiscales applicables aux associations (TVA, impots commerciaux si applicable)
  • +
  • RGPD : protection des donnees personnelles, designation d'un DPO (DPO-167945)
  • +
  • Reglement eIDAS : conformite des signatures electroniques
  • +
  • Code de commerce : conservation des documents comptables pendant 10 ans
  • +
  • Code de la consommation : respect des droits des clients et des obligations d'information
  • +
+
+ +
+

3. Transparence

+

L'association E-Cosplay s'engage a une transparence complete sur son fonctionnement :

+
    +
  • Statuts : les statuts de l'association sont accessibles sur demande a l'adresse contact@e-cosplay.fr
  • +
  • Gouvernance : l'association est dirigee par un bureau compose d'un president, d'un tresorier et d'un secretaire, elus par l'assemblee generale
  • +
  • Assemblee generale : une assemblee generale ordinaire est tenue chaque annee. Les decisions sont consignees dans un proces-verbal
  • +
  • Comptabilite : une comptabilite reguliere est tenue conformement aux obligations legales. Les comptes annuels sont presentes et approuves en assemblee generale
  • +
  • Tarifs : l'ensemble des tarifs est publie sur la page Tarifs
  • +
  • CGV et CGU : les conditions generales sont publiees et accessibles a tout moment sur la plateforme
  • +
+
+ +
+

4. Confidentialite des donnees commerciales

+

Bien que l'association s'engage a la transparence sur son fonctionnement, certaines informations restent strictement confidentielles pour proteger ses clients et prestataires :

+
    +
  • Le chiffre d'affaires genere par les activites commerciales de l'association n'est pas communique publiquement
  • +
  • Les donnees financieres detaillees (revenus par service, marges, commissions) sont reservees aux membres du bureau
  • +
  • Les informations relatives aux clients et prestataires (identite, contrats, volumes, conditions particulieres) sont protegees par une obligation de confidentialite
  • +
  • Les donnees personnelles sont traitees conformement au RGPD et a notre Politique de confidentialite
  • +
+

Cette confidentialite est necessaire pour garantir la protection commerciale de nos partenaires et le respect de leurs interets.

+
+ +
+

5. Protection des donnees

+

L'association met en oeuvre des mesures techniques et organisationnelles pour garantir la securite et la confidentialite des donnees :

+
    +
  • Chiffrement des communications (TLS/HTTPS)
  • +
  • Hashage des mots de passe (bcrypt)
  • +
  • Double authentification (2FA) pour tous les comptes
  • +
  • Signature electronique S/MIME des emails
  • +
  • Protection DDoS et WAF via Cloudflare
  • +
  • Hebergement des donnees dans l'Union Europeenne (Google Cloud Platform, region Europe)
  • +
  • Sauvegardes regulieres et chiffrees
  • +
  • Acces restreint aux donnees (principe du moindre privilege)
  • +
+
+ +
+

6. Qualite et securite du code

+

La plateforme CRM Ecosplay est testee et deployee via un processus d'integration et de deploiement continus (CI/CD) automatise, garantissant la conformite, la proprete et la securite du code a chaque mise a jour :

+
    +
  • Pipeline CI/CD automatise : chaque modification du code est soumise a une serie de verifications automatiques avant mise en production
  • +
  • Tests automatises : execution des tests unitaires et fonctionnels (PHPUnit) pour verifier le bon fonctionnement de l'application
  • +
  • Analyse statique du code : verification de la qualite et de la conformite du code via PHPStan et PHP CS Fixer
  • +
  • Analyse de securite SonarQube : chaque version est analysee par SonarQube pour detecter les vulnerabilites de securite, les bugs, les code smells et les duplications de code. Le resultat de l'analyse conditionne le deploiement en production
  • +
  • Audit des dependances : verification automatique des vulnerabilites connues dans les dependances (Composer Audit, OWASP Dependency-Check)
  • +
  • Analyse des Dockerfiles : verification des bonnes pratiques de securite des conteneurs via Hadolint
  • +
  • Lint des templates et configurations : verification automatique de la syntaxe des templates Twig, des fichiers YAML et du container Symfony
  • +
+

Ce processus garantit qu'aucune modification ne peut etre deployee en production sans avoir passe l'ensemble des controles de qualite et de securite.

+
+ Quality Gate + Coverage + Duplicated Lines + Lines of Code + Security Hotspots + Reliability Issues + Maintainability Issues + Security Issues + Maintainability Rating + Reliability Rating + Security Rating + Technical Debt +
+
+ +
+

7. Securite des paiements

+

L'association E-Cosplay met en oeuvre les mesures necessaires pour garantir la securite des transactions financieres :

+
    +
  • HTTPS de bout en bout : l'ensemble des communications sont chiffrees via HTTPS, avec un certificat TLS fourni par Cloudflare en mode Full (Strict), garantissant un chiffrement de bout en bout entre le navigateur de l'utilisateur, Cloudflare et le serveur d'origine
  • +
  • Paiements securises par Stripe : tous les paiements sont traites par Stripe Payments Europe, Ltd. (Dublin, Irlande), certifie PCI DSS Level 1, le plus haut niveau de certification de securite dans l'industrie des paiements. Aucune donnee bancaire n'est stockee sur nos serveurs
  • +
  • Stripe Connect : les reversements aux revendeurs sont geres via Stripe Connect, assurant la conformite reglementaire et la tracabilite des flux financiers
  • +
  • Protection anti-fraude : Stripe Radar est active pour detecter et bloquer les transactions frauduleuses en temps reel
  • +
  • 3D Secure : l'authentification forte (SCA) est appliquee conformement a la directive europeenne DSP2 pour les paiements par carte
  • +
+
+ +
+

8. Delegue a la Protection des Donnees

+

Un Delegue a la Protection des Donnees (DPO) a ete designe conformement au RGPD :

+ +
+ +
+

9. Signalement et reclamations

+

Pour toute question relative a la conformite de l'association, ou pour signaler un manquement :

+
    +
  • Email : contact@e-cosplay.fr
  • +
  • Courrier : Association E-Cosplay, 42 rue de Saint-Quentin, 02800 Beautor, France
  • +
+

En cas de litige non resolu, vous pouvez saisir la CNIL pour les questions relatives aux donnees personnelles (www.cnil.fr) ou les tribunaux competents de Laon (Aisne).

+
+ +

Derniere mise a jour : 01/04/2026

+
+
+{% endblock %} diff --git a/templates/legal/cookie.html.twig b/templates/legal/cookie.html.twig new file mode 100644 index 0000000..06e1e43 --- /dev/null +++ b/templates/legal/cookie.html.twig @@ -0,0 +1,86 @@ +{% extends 'legal/_layout.html.twig' %} + +{% block title %}Politique de Cookies - CRM Ecosplay{% endblock %} +{% block description %}Politique de cookies du site CRM Ecosplay.{% endblock %} + +{% block body %} +
+

Politique de Cookies

+ +
+ +
+

1. Qu'est-ce qu'un cookie ?

+

Un cookie est un petit fichier texte depose sur votre terminal (ordinateur, tablette, smartphone) lors de la visite d'un site web. Il permet au site de memoriser des informations relatives a votre navigation (preferences, session, etc.).

+
+ +
+

2. Cookies utilises sur la Plateforme

+

La plateforme CRM Ecosplay utilise exclusivement des cookies strictement necessaires au fonctionnement du site :

+
    +
  • Cookie de session : permet de maintenir votre connexion et votre navigation sur la Plateforme. Il est supprime a la fermeture du navigateur.
  • +
  • Cookie de securite (CSRF) : protege contre les attaques de type Cross-Site Request Forgery lors de la soumission de formulaires.
  • +
  • Cookie de preference : memorise vos choix (langue, theme) pour ameliorer votre experience utilisateur.
  • +
+
+ +
+

3. Cookies tiers

+

La Plateforme peut integrer des services tiers qui deposent leurs propres cookies :

+
    +
  • Stripe : pour le traitement securise des paiements. Ces cookies sont necessaires au fonctionnement du module de paiement.
  • +
  • Cloudflare : pour la securite et la performance du site (protection DDoS, CDN). Ces cookies sont strictement techniques.
  • +
+

La Plateforme n'utilise aucun cookie publicitaire, de tracking ou d'analyse comportementale (pas de Google Analytics, Facebook Pixel, etc.).

+
+ +
+

4. Duree de conservation

+
    +
  • Cookies de session : supprimes a la fermeture du navigateur
  • +
  • Cookies de securite (Cloudflare) : duree maximale de 24 heures
  • +
  • Cookies Stripe : selon la politique de Stripe, generalement le temps de la transaction
  • +
+
+ +
+

5. Gestion des cookies

+

Etant donne que la Plateforme utilise uniquement des cookies strictement necessaires, aucun consentement prealable n'est requis conformement a l'article 82 de la loi Informatique et Libertes et aux recommandations de la CNIL.

+

Vous pouvez toutefois configurer votre navigateur pour refuser les cookies. Veuillez noter que la desactivation des cookies necessaires peut empecher le bon fonctionnement de la Plateforme (connexion, paiement, etc.).

+

Pour configurer les cookies dans votre navigateur :

+
    +
  • Chrome : Parametres > Confidentialite et securite > Cookies
  • +
  • Firefox : Parametres > Vie privee et securite > Cookies
  • +
  • Safari : Preferences > Confidentialite > Cookies
  • +
  • Edge : Parametres > Cookies et autorisations de site
  • +
+
+ +
+

6. Base legale

+

Le depot de cookies strictement necessaires repose sur l'interet legitime de l'editeur a assurer le fonctionnement et la securite de la Plateforme, conformement a :

+
    +
  • Article 82 de la loi n°78-17 du 6 janvier 1978 (Informatique et Libertes)
  • +
  • Directive 2002/58/CE (directive ePrivacy)
  • +
  • Recommandations de la CNIL sur les cookies et traceurs
  • +
+
+ +
+

7. Delegue a la Protection des Donnees

+

Pour toute question relative aux cookies ou a vos donnees personnelles :

+ +
+ +
+

8. Droit applicable

+

Tout litige en relation avec l'utilisation des cookies sur la Plateforme est soumis au droit francais. Il est fait attribution exclusive de juridiction aux tribunaux competents de Laon.

+
+ +

Derniere mise a jour : 30/03/2026

+
+
+{% endblock %} diff --git a/templates/legal/hebergement.html.twig b/templates/legal/hebergement.html.twig new file mode 100644 index 0000000..fc3e1e5 --- /dev/null +++ b/templates/legal/hebergement.html.twig @@ -0,0 +1,80 @@ +{% extends 'legal/_layout.html.twig' %} + +{% block title %}Hebergement - CRM Ecosplay{% endblock %} +{% block description %}Informations d'hebergement du site CRM Ecosplay.{% endblock %} + +{% block body %} +
+

Hebergement

+ +
+ +
+

1. Hebergeur principal

+

Le site crm.e-cosplay.fr est heberge par :

+
    +
  • Google Cloud Platform (GCP)
  • +
  • Google Ireland Limited
  • +
  • Gordon House, Barrow Street, Dublin 4, Irlande
  • +
  • Region : europe-west1 (Belgique)
  • +
  • Site : cloud.google.com
  • +
+
+ +
+

2. CDN et securite

+
    +
  • Cloudflare, Inc.
  • +
  • 101 Townsend St, San Francisco, CA 94107, Etats-Unis
  • +
  • Services : CDN, protection DDoS, WAF, gestion DNS, certificats TLS
  • +
  • Site : cloudflare.com
  • +
+
+ +
+

3. Stockage des fichiers

+
    +
  • Stockage objet compatible S3
  • +
  • Auto-heberge sur le serveur
  • +
  • Localisation : Europe
  • +
  • Utilisation : stockage des images, documents et fichiers uploades
  • +
+
+ +
+

4. Service d'envoi d'emails

+
    +
  • Amazon Simple Email Service (SES)
  • +
  • Amazon Web Services EMEA SARL
  • +
  • 38 Avenue John F. Kennedy, L-1855 Luxembourg
  • +
  • Region : eu-west-3 (Paris)
  • +
  • Utilisation : envoi des emails transactionnels (confirmations, notifications)
  • +
+
+ +
+

5. Service de paiement

+
    +
  • Stripe Payments Europe, Ltd.
  • +
  • 1 Grand Canal Street Lower, Grand Canal Dock, Dublin 2, Irlande
  • +
  • Utilisation : traitement des paiements en ligne, gestion des remboursements
  • +
  • Certification : PCI DSS Level 1
  • +
  • Site : stripe.com
  • +
+
+ +
+

6. Localisation des donnees

+

L'ensemble des donnees de la Plateforme (base de donnees, fichiers, sauvegardes) est heberge dans l'Union Europeenne.

+

Les sous-traitants americains (Cloudflare, Stripe) operent sous le cadre du Data Privacy Framework (DPF) et/ou des Clauses Contractuelles Types (CCT) pour les transferts de donnees hors UE.

+
+ +
+

7. Contact

+

Pour toute question relative a l'hebergement : contact@e-cosplay.fr

+
+ +

Derniere mise a jour : 30/03/2026

+
+
+{% endblock %} diff --git a/templates/legal/mention_legal.html.twig b/templates/legal/mention_legal.html.twig new file mode 100644 index 0000000..2accf39 --- /dev/null +++ b/templates/legal/mention_legal.html.twig @@ -0,0 +1,80 @@ +{% extends 'legal/_layout.html.twig' %} + +{% block title %}Mentions Legales - CRM Ecosplay{% endblock %} +{% block description %}Mentions legales du site CRM Ecosplay.{% endblock %} + +{% block body %} +
+

Mentions Legales

+ +
+ +
+

1. Editeur du site

+

Le site crm.e-cosplay.fr (ci-apres "la Plateforme") est edite par :

+
    +
  • Association E-Cosplay
  • +
  • RNA : W022006988
  • +
  • SIREN : 943121517
  • +
  • Siege social : 42 rue de Saint-Quentin, 02800 Beautor, France
  • +
  • Email : contact@e-cosplay.fr
  • +
  • Telephone : 06 79 34 88 02
  • +
  • Site web : www.e-cosplay.fr
  • +
  • Directeur de la publication : Serreau Jovann
  • +
+
+ +
+

2. Hebergement

+

Le site est heberge par :

+
    +
  • Google Cloud Platform
  • +
  • Google Ireland Limited, Gordon House, Barrow Street, Dublin 4, Irlande
  • +
+

Le nom de domaine est gere via Cloudflare, Inc., 101 Townsend St, San Francisco, CA 94107, Etats-Unis.

+
+ +
+

3. Nature de la Plateforme

+

La Plateforme CRM Ecosplay est un outil de gestion interne de l'association E-Cosplay, destine a la gestion de la relation client et prestataire.

+
+ +
+

4. Paiement

+

Les paiements en ligne sont securises par Stripe (Stripe Payments Europe, Ltd., 1 Grand Canal Street Lower, Dublin 2, Irlande). La Plateforme ne stocke aucune donnee bancaire.

+
+ +
+

5. Propriete intellectuelle

+

L'ensemble du contenu de la Plateforme (textes, graphismes, logos, icones, code source) est la propriete exclusive de l'association E-Cosplay, sauf mention contraire. Toute reproduction, meme partielle, est interdite sans autorisation ecrite prealable.

+
+ +
+

6. Donnees personnelles

+

Conformement au Reglement General sur la Protection des Donnees (RGPD), l'association E-Cosplay s'engage a proteger la confidentialite des donnees personnelles collectees. Pour toute information ou exercice de vos droits Informatique et Libertes sur les traitements de donnees personnelles, vous pouvez contacter notre Delegue a la Protection des Donnees (DPO).

+ +

Pour plus d'informations, consultez notre Politique de confidentialite.

+
+ +
+

7. Cookies

+

La Plateforme utilise des cookies strictement necessaires a son fonctionnement. Pour plus de details, consultez notre Politique de cookies.

+
+ +
+

8. Droit applicable et litiges

+

Les presentes mentions legales sont regies par le droit francais. En cas de litige, les tribunaux competents de Laon (Aisne) seront seuls competents.

+
+ +
+

9. Contact

+

Pour toute question relative aux presentes mentions legales, vous pouvez nous contacter a l'adresse : contact@e-cosplay.fr.

+
+ +

Derniere mise a jour : 30/03/2026

+
+
+{% endblock %} diff --git a/templates/legal/rgpd.html.twig b/templates/legal/rgpd.html.twig new file mode 100644 index 0000000..17cad51 --- /dev/null +++ b/templates/legal/rgpd.html.twig @@ -0,0 +1,209 @@ +{% extends 'legal/_layout.html.twig' %} + +{% block title %}Politique de Confidentialite - CRM Ecosplay{% endblock %} +{% block description %}Politique de protection des donnees personnelles du CRM Ecosplay.{% endblock %} + +{% block body %} +
+

Politique de Confidentialite

+ + + +
+ +
+

1. Responsable du traitement

+

Le responsable du traitement des donnees personnelles est :

+
    +
  • Association E-Cosplay
  • +
  • SIREN : 943121517 / RNA : W022006988
  • +
  • 42 rue de Saint-Quentin, 02800 Beautor, France
  • +
  • Email : contact@e-cosplay.fr
  • +
+
+ +
+

2. Delegue a la Protection des Donnees (DPO)

+

Conformement au RGPD, un Delegue a la Protection des Donnees a ete designe :

+ +
+ +
+

3. Donnees collectees

+

La Plateforme collecte les donnees suivantes :

+ +

Lors de la creation de compte :

+
    +
  • Nom et prenom
  • +
  • Adresse email
  • +
  • Mot de passe (stocke sous forme hashee)
  • +
+ +

Lors de l'achat de billets :

+
    +
  • Nom et prenom de l'acheteur
  • +
  • Adresse email
  • +
  • Donnees de paiement (traitees exclusivement par Stripe, non stockees sur nos serveurs)
  • +
+ +

Donnees techniques :

+
    +
  • Adresse IP (anonymisee via Cloudflare)
  • +
  • Cookies strictement necessaires (voir Politique de cookies)
  • +
+
+ +
+

4. Finalites du traitement

+
    +
  • Gestion des comptes utilisateurs
  • +
  • Traitement des commandes et emission des billets
  • +
  • Envoi des billets et confirmations par email
  • +
  • Communication liee aux evenements achetes (modifications, annulations)
  • +
  • Securite de la Plateforme et prevention des fraudes
  • +
  • Respect des obligations legales et reglementaires
  • +
+
+ +
+

5. Bases legales

+
    +
  • Execution du contrat : traitement des commandes, emission des billets, gestion du compte
  • +
  • Obligation legale : conservation des donnees de facturation
  • +
  • Interet legitime : securite de la Plateforme, prevention des fraudes
  • +
  • Consentement : envoi de communications commerciales (newsletter)
  • +
+
+ +
+

6. Destinataires des donnees

+

Vos donnees personnelles sont accessibles par :

+
    +
  • L'association E-Cosplay : administration de la Plateforme
  • +
  • Les organisateurs d'evenements : uniquement les donnees necessaires a la gestion de leurs evenements (nom, email de l'acheteur)
  • +
  • Stripe : traitement des paiements (Stripe Payments Europe, Ltd., Dublin, Irlande)
  • +
  • Amazon Web Services (SES) : envoi des emails transactionnels (region eu-west-3, Irlande)
  • +
  • Google Cloud Platform : hebergement des donnees (region Europe)
  • +
  • Cloudflare : securite et CDN
  • +
+

Aucune donnee n'est vendue ou cedee a des tiers a des fins commerciales ou publicitaires.

+
+ +
+

7. Transferts hors UE

+

Certains sous-traitants (Stripe, Cloudflare) peuvent transferer des donnees en dehors de l'Union Europeenne. Ces transferts sont encadres par :

+
    +
  • Les Clauses Contractuelles Types (CCT) de la Commission Europeenne
  • +
  • Le Data Privacy Framework (DPF) UE-US pour les entreprises certifiees
  • +
+
+ +
+

8. Duree de conservation

+
    +
  • Donnees de compte : conservees pendant la duree d'existence du compte, puis 3 ans apres la derniere activite
  • +
  • Donnees de transaction : 10 ans (obligation legale comptable)
  • +
  • Donnees de connexion (logs) : 12 mois
  • +
  • Donnees de prospection : 3 ans apres le dernier contact
  • +
+
+ +
+

9. Securite des donnees

+

L'association E-Cosplay met en oeuvre des mesures techniques et organisationnelles appropriees pour garantir la securite des donnees :

+
    +
  • Chiffrement des communications (TLS/HTTPS)
  • +
  • Hashage des mots de passe (bcrypt)
  • +
  • Signature S/MIME des emails
  • +
  • Protection DDoS et WAF via Cloudflare
  • +
  • Acces restreint aux donnees (principe du moindre privilege)
  • +
  • Sauvegardes regulieres et chiffrees
  • +
+
+ +
+

10. Vos droits

+

Conformement au RGPD, vous disposez des droits suivants :

+
    +
  • Droit d'acces : obtenir une copie de vos donnees personnelles
  • +
  • Droit de rectification : corriger des donnees inexactes ou incompletes
  • +
  • Droit a l'effacement : demander la suppression de vos donnees (sous reserve des obligations legales)
  • +
  • Droit a la limitation : limiter le traitement dans certains cas
  • +
  • Droit a la portabilite : recevoir vos donnees dans un format structure et lisible
  • +
  • Droit d'opposition : vous opposer au traitement de vos donnees pour des motifs legitimes
  • +
  • Droit de retrait du consentement : retirer votre consentement a tout moment (newsletter, etc.)
  • +
+

Pour exercer vos droits, contactez le DPO a l'adresse contact@e-cosplay.fr en precisant votre identite. Une reponse vous sera apportee dans un delai maximum de 30 jours.

+
+ +
+

11. Reclamation

+

Si vous estimez que le traitement de vos donnees personnelles constitue une violation du RGPD, vous avez le droit d'introduire une reclamation aupres de la Commission Nationale de l'Informatique et des Libertes (CNIL) :

+
    +
  • CNIL - 3 Place de Fontenoy, TSA 80715, 75334 Paris Cedex 07
  • +
  • Site : www.cnil.fr
  • +
+
+ +
+

12. Droit applicable

+

Tout litige en relation avec le traitement des donnees personnelles est soumis au droit francais. Il est fait attribution exclusive de juridiction aux tribunaux competents de Laon.

+
+ +

Derniere mise a jour : 30/03/2026

+ +
+

Exercer vos droits

+ + {% for type, messages in app.flashes %} + {% for message in messages %} +
+ {{ message }} +
+ {% endfor %} + {% endfor %} + +
+
+

Droit d'acces

+

Recevez par email un PDF contenant toutes les donnees de navigation que nous detenons vous concernant (article 15 du RGPD).

+
+
+ + +
+
+ + +
+ +
+
+ +
+

Droit a l'effacement

+

Supprimez toutes vos donnees de navigation de nos systemes et recevez une attestation par email (article 17 du RGPD).

+
+
+ + +
+
+ + +
+ +
+
+
+
+
+
+{% endblock %} diff --git a/templates/legal/tarif.html.twig b/templates/legal/tarif.html.twig new file mode 100644 index 0000000..531be07 --- /dev/null +++ b/templates/legal/tarif.html.twig @@ -0,0 +1,447 @@ +{% extends 'legal/_layout.html.twig' %} + +{% block title %}Tarifs des services - CRM Ecosplay{% endblock %} +{% block description %}Consultez la grille tarifaire des services proposes par l'association E-Cosplay : site internet, billetterie, email, mailing, signature electronique, nom de domaine.{% endblock %} + +{% block og_title %}Tarifs des services - CRM Ecosplay{% endblock %} +{% block og_description %}Grille tarifaire des services E-Cosplay : hebergement web, billetterie en ligne, messagerie, mailing, signature electronique et noms de domaine.{% endblock %} + +{% block head %} + +{% endblock %} + +{% block body %} +
+

Tarifs

+ +
+

Tous les tarifs sont exprimes en euros hors taxes (€ HT). Les tarifs ci-dessous sont indicatifs et susceptibles d'evolution. Conformement a nos Conditions Generales de Vente (article 4), toute modification tarifaire sera communiquee aux clients concernes au minimum 1 mois avant son application. Le tarif en vigueur a la signature du contrat est garanti pendant 6 mois.

+ +
+

Service de Site Internet - CMS Esy-Web

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
PrestationTarifPeriodicite
Creation du site internet200,00 €Unique
+ Hebergement, securite et mise a jour + Inclus : hebergement, certificat SSL, mises a jour du CMS, maintenance technique, sauvegardes + 50,00 €/ mois
+ Support premium et aide au remplissage + Inclus : aide a la mise en page et au remplissage du contenu, support prioritaire + 100,00 €/ mois
+
+
+ +
+

Service de Billetterie en ligne

+ +
+
+

Gratuit

+

0 €

+

Aucun frais fixe

+
    +
  • ✓ 1 evenement
  • +
  • ✓ Billets standards
  • +
  • ✓ QR code et verification
  • +
  • ✓ Paiement Stripe
  • +
  • ✓ Email de confirmation
  • +
  • ✗ Evenements illimites
  • +
  • ✗ Generation PDF
  • +
  • ✗ Image par billet
  • +
  • ✗ Design personnalise
  • +
+
+ +
+ Populaire +

Basic

+

20 € / mois

+

+ commission sur les ventes

+
    +
  • ✓ Evenements illimites
  • +
  • ✓ Billets standards
  • +
  • ✓ QR code et verification
  • +
  • ✓ Paiement Stripe
  • +
  • ✓ Email de confirmation
  • +
  • ✓ Generation PDF automatique
  • +
  • ✓ Invitations gratuites
  • +
  • ✓ Reservation brocantes
  • +
  • ✓ Vote en ligne
  • +
  • ✗ Image par billet
  • +
  • ✗ Design personnalise
  • +
+
+ +
+

Sur-mesure

+

Sur devis

+

Commission et fonctionnalites personnalisees

+
    +
  • ✓ Evenements illimites
  • +
  • ✓ Design de billets personnalise
  • +
  • ✓ Image par billet
  • +
  • ✓ QR code et verification
  • +
  • ✓ Generation PDF automatique
  • +
  • ✓ Categories illimitees
  • +
  • ✓ Invitations gratuites
  • +
  • ✓ Reservation brocantes
  • +
  • ✓ Vote en ligne
  • +
  • ✓ Accompagnement dedie
  • +
+
+
+ +
+
+ Commissions +
+ + + + + + + + + + + +
+ Commission E-Ticket + Par transaction, sur le montant HT — Negociable selon votre profil + 3 %
+ Frais Stripe + Par transaction, cartes europeennes + lien de paiement + 1,5 % + 0,25 €
+
+
+ +
+

Service aux Associations

+ +
+
+

Basic

+

25 € / mois

+

Gestion associative essentielle

+
    +
  • ✓ Gestion des adherents
  • +
  • ✓ Assemblees generales
  • +
  • ✓ Tableau de bord
  • +
  • ✓ Export des donnees
  • +
  • ✗ Signature electronique
  • +
  • ✗ Paiement en ligne
  • +
  • ✗ Envoi de mails (convocations, etc.)
  • +
+
+ +
+ Complet +

Premium

+

50 € / mois

+

Tout le Basic + outils avances

+
    +
  • ✓ Gestion des adherents
  • +
  • ✓ Assemblees generales
  • +
  • ✓ Tableau de bord
  • +
  • ✓ Export des donnees
  • +
  • ✓ Signature electronique des documents
  • +
  • ✓ Paiement en ligne (cotisations, dons)
  • +
  • ✓ Envoi de mails (convocations AG, relances, notifications)
  • +
+
+
+
+ +
+

Service de Mail

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PrestationTarifPeriodicite
+ 1 boite mail (5 Go) + Inclus : webmail, IMAP/SMTP, antispam, certificat SSL + 5,00 €/ mois
Boite mail supplementaire (5 Go)1,00 €/ mois / boite
+ Stockage supplementaire (+10 Go) + Par tranche de 10 Go, applicable par boite mail + 5,00 €/ mois / boite
+ Redirections et alias + Nombre illimite de redirections et alias email + Offert
+
+
+ +
+

Service de Mailing

+
+ + + + + + + + + + + + + + + +
PrestationTarifPeriodicite
+ Campagnes emailing illimitees + Envois illimites, creation de campagnes, statistiques, gestion des listes + 50,00 €/ mois
+
+
+

Prerequis : l'abonnement au Service de Mail est obligatoire pour beneficier du Service de Mailing.

+

Recommandation : la gestion du nom de domaine via notre Service de Nom de Domaine est fortement recommandee pour une configuration simplifiee (DNS, SPF, DKIM, DMARC).

+
+
+ +
+

Service de Signature Electronique

+
+ + + + + + + + + + + + + + + +
PrestationTarifPeriodicite
+ Signature electronique illimitee + Inclus : signatures illimitees, certificat de signature, journal d'audit, verification en ligne, propulse par DocuSeal + 20,00 €/ mois
+
+
+ +
+

Service de Nom de Domaine

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
PrestationTarifPeriodicite
+ Depot de nom de domaine + Enregistrement initial du nom de domaine aupres de la SAS OVHcloud, zone DNS geree via Cloudflare, configuration incluse + 25,00 €Unique
+ Renouvellement + Renouvellement annuel du nom de domaine + 30,00 €/ an
+ Reactivation + Reactivation d'un nom de domaine expire + 50,00 €Unique
+
+
+ +
+

Commissions Revendeur (Prestataire)

+
+
+ Commissions sur ventes +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Service venduCommissionType
Site Internet (creation)100,00 €/ site vendu
Hebergement20,00 €/ mois / client
Mailing20,00 €/ mois / client
Mail2,00 €/ mois / client
+
+

Les commissions sont versees mensuellement au revendeur. Les conditions detaillees sont precisees dans le contrat de partenariat.

+
+ +

Derniere mise a jour : 31/03/2026

+
+
+{% endblock %} diff --git a/templates/pdf/contrat_revendeur.html.twig b/templates/pdf/contrat_revendeur.html.twig new file mode 100644 index 0000000..2a6cb1f --- /dev/null +++ b/templates/pdf/contrat_revendeur.html.twig @@ -0,0 +1,212 @@ + + + + + + + + +
+

Contrat de Partenariat Revendeur

+
Reference : {{ revendeur.codeRevendeur }} — Date : {{ date|date('d/m/Y') }}
+ +

Entre les soussignes :

+ + + + + + +
+ Le Prestataire + Association E-Cosplay
+ RNA : W022006988 — SIREN : 943121517
+ 42 rue de Saint-Quentin, 02800 Beautor, France
+ contact@e-cosplay.fr +
+ Le Revendeur + {{ revendeur.raisonSociale ?? revendeur.user.fullName }}
+ {% if revendeur.siret %}SIRET : {{ revendeur.siret }}
{% endif %} + {% if revendeur.address %}{{ revendeur.address }}{% if revendeur.zipCode %}, {{ revendeur.zipCode }}{% endif %}{% if revendeur.city %} {{ revendeur.city }}{% endif %}
{% endif %} + {{ revendeur.email ?? revendeur.user.email }} +
+ +

Il a ete convenu ce qui suit :

+ +
+

Article 1 — Objet

+

Le present contrat definit les conditions dans lesquelles le Revendeur apporte des clients a l'Association E-Cosplay (ci-apres "l'Association"). Le role du Revendeur consiste exclusivement a prospecter et orienter des clients potentiels vers l'Association.

+

L'Association prend en charge l'integralite de la relation client, de la souscription a la resiliation, incluant la realisation des prestations, la facturation, le support technique et le service apres-vente. Le Revendeur n'intervient a aucun moment dans l'execution des services.

+
+ +
+

Article 2 — Services concernes

+

Le Revendeur est autorise a prospecter des clients pour les services suivants :

+
    +
  • Service de Site Internet (CMS Esy-Web)
  • +
  • Service de Mail
  • +
  • Service de Mailing
  • +
  • Service de Nom de Domaine
  • +
+

La liste des services pourra etre etendue ou restreinte par l'Association, avec notification prealable au Revendeur.

+
+ +
+

Article 3 — Role du Revendeur

+

Le Revendeur s'engage a :

+
    +
  • Prospecter des clients potentiels et les orienter vers l'Association
  • +
  • Presenter les services de l'Association de maniere fidele et non trompeuse
  • +
  • Transmettre les coordonnees des clients interesses a l'Association
  • +
+

Le Revendeur n'est pas autorise a conclure de contrat, devis, ou engagement au nom de l'Association. Toute souscription est validee directement par l'Association apres verification (conformement aux CGV, article 6).

+
+ +
+

Article 4 — Bonne foi et informations exactes

+

Le Revendeur s'engage a communiquer aux clients potentiels des informations exactes, completes et non trompeuses concernant les prestations, les tarifs, les conditions de service et les engagements de l'Association.

+

En cas de manquement a cette obligation, notamment si le Revendeur transmet des informations mensongeres, erronees ou trompeuses sur les prestations, les tarifs ou toute autre condition, que ce soit avant ou apres la signature du present contrat :

+
    +
  • Le contrat entre l'Association et le client concerne sera resilie de plein droit aux torts exclusifs du Revendeur
  • +
  • Le contrat entre l'Association et le Revendeur sera resilie immediatement, sans preavis ni indemnite
  • +
  • L'Association se reserve le droit d'engager des poursuites penales a l'encontre du Revendeur, notamment sur le fondement des articles 313-1 et suivants du Code penal (escroquerie) et L121-1 du Code de la consommation (pratiques commerciales trompeuses)
  • +
  • L'Association conseillera au client lese d'engager ses propres poursuites a l'encontre du Revendeur. En cas de reclamation ou d'action judiciaire du client contre l'Association resultant des agissements du Revendeur, ce dernier sera tenu de garantir et indemniser integralement l'Association de toute condamnation, frais de justice et dommages-interets
  • +
  • Toutes les commissions versees au titre des clients concernes seront restituees a l'Association
  • +
+

Cette clause s'applique pendant toute la duree du contrat et survit a sa cessation dans un delai de 30 jours.

+
+ +
+

Article 5 — Role de l'Association

+ +

L'Association s'engage a :

+
    +
  • Prendre en charge l'integralite de la relation avec le client apporte par le Revendeur
  • +
  • Realiser les prestations conformement aux CGV et au contrat de service signe avec le client
  • +
  • Emettre les factures et encaisser les paiements directement aupres du client
  • +
  • Assurer le support technique et le service apres-vente
  • +
  • Informer le Revendeur de la validation ou du refus du client
  • +
+
+ +
+

Article 6 — Commissions

+

Le Revendeur percoit une commission pour chaque client apporte dont la souscription a ete validee par l'Association et dont la facture a ete effectivement reglee par le client.

+

Conditions de versement :

+
    +
  • La commission est due uniquement apres paiement integral de la facture par le client
  • +
  • En cas de non-paiement, d'impaye ou de remboursement, aucune commission n'est due
  • +
  • Les commissions sont versees mensuellement, le mois suivant l'encaissement, par l'un des moyens suivants : virement bancaire, Stripe Connect ou especes (dans la limite legale de 1 000 € par transaction conformement a l'article L112-6 du Code monetaire et financier)
  • +
  • Le detail des taux de commission en vigueur est consultable a l'adresse : https://crm.e-cosplay.fr/legal/tarif
  • +
  • Les taux de commission sont susceptibles d'evolution avec un preavis d'un mois
  • +
+
+ +
+

Article 7 — Duree et resiliation

+

Le present contrat est conclu pour une duree indeterminee. Chaque partie peut le resilier par ecrit avec un preavis d'un mois. En cas de manquement grave aux obligations du present contrat, la resiliation peut intervenir sans preavis apres mise en demeure restee infructueuse pendant 15 jours.

+

En cas de resiliation, les commissions dues pour les clients deja apportes et dont les factures sont reglees restent exigibles. Le Revendeur ne pourra pretendre a aucune commission sur les renouvellements ou nouvelles souscriptions intervenant apres la date de resiliation.

+
+ +
+

Article 8 — Responsabilite

+

Le Revendeur est seul responsable de la relation commerciale avec ses clients finaux. L'association ne pourra en aucun cas etre tenue responsable des engagements pris par le Revendeur aupres de ses clients, au-dela des prestations prevues par les CGV.

+

Le Revendeur s'engage a indemniser l'association de tout prejudice, frais ou condamnation resultant d'un manquement a ses obligations contractuelles ou d'une reclamation d'un client final imputable a son activite.

+
+ +
+

Article 9 — Propriete intellectuelle

+

Le Revendeur n'acquiert aucun droit de propriete intellectuelle sur les services, logiciels, marques ou contenus de l'association. Il est autorise a utiliser la marque E-Cosplay uniquement dans le cadre de la commercialisation des services prevus au present contrat, et dans le respect des directives de l'association.

+
+ +
+

Article 10 — Non-concurrence

+

Pendant la duree du contrat et pendant une periode de 6 mois apres sa cessation, le Revendeur s'engage a ne pas developper, commercialiser ou promouvoir un service directement concurrent des services de l'association, sauf accord ecrit prealable.

+
+ +
+

Article 11 — Protection des donnees

+

Le Revendeur s'engage a respecter le Reglement General sur la Protection des Donnees (RGPD) dans le cadre de son activite. Il ne pourra utiliser les donnees personnelles des clients finaux qu'aux fins strictement necessaires a l'execution du present contrat. Toute violation du RGPD par le Revendeur entrainera la resiliation immediate du contrat.

+
+ +
+

Article 12 — Confidentialite

+

Chaque partie s'engage a ne pas divulguer les informations confidentielles portees a sa connaissance dans le cadre du present contrat, notamment les tarifs, commissions, conditions commerciales, donnees clients et toute information technique ou strategique. Cette obligation survit a la cessation du contrat pour une duree de 2 ans.

+
+ +
+

Article 13 — Force majeure

+

Aucune des parties ne pourra etre tenue responsable d'un manquement a ses obligations contractuelles si ce manquement resulte d'un cas de force majeure tel que defini par l'article 1218 du Code civil (catastrophe naturelle, pandemie, guerre, greve, panne generalisee, decision gouvernementale, etc.).

+
+ +
+

Article 14 — Assurance

+

Le Revendeur declare etre titulaire d'une assurance responsabilite civile professionnelle couvrant son activite. Il s'engage a maintenir cette assurance pendant toute la duree du contrat et a en fournir une attestation sur demande de l'association.

+
+ +
+

Article 15 — Integralite de l'accord

+

Le present contrat constitue l'integralite de l'accord entre les parties et annule et remplace tout accord, arrangement ou negociation anterieur relatif a son objet. Toute modification du present contrat devra faire l'objet d'un avenant ecrit signe par les deux parties.

+
+ +
+

Article 16 — Droit applicable et litiges

+

Le present contrat est regi par le droit francais. En cas de litige, les parties s'efforceront de trouver une solution amiable dans un delai de 30 jours. A defaut d'accord amiable, les tribunaux competents de Laon (Aisne) seront seuls competents, quel que soit le lieu de domicile du Revendeur.

+
+ +

Fait en deux exemplaires, a Beautor, le {{ date|date('d/m/Y') }}.

+ + + + + + +
+ Pour l'Association E-Cosplay +
+
{% verbatim %}{{Sign;type=signature;width=150;height=50;role=E-Cosplay}}{% endverbatim %}
+
+ Date et signature +
+ Pour le Revendeur +
+
{% verbatim %}{{Sign2;type=signature;width=150;height=50;role=Revendeur}}{% endverbatim %}
+
+ Date et signature +
+ + +
+ + diff --git a/templates/pdf/rgpd_access.html.twig b/templates/pdf/rgpd_access.html.twig new file mode 100644 index 0000000..1c54232 --- /dev/null +++ b/templates/pdf/rgpd_access.html.twig @@ -0,0 +1,104 @@ + + + + + + + + +
+ Droit d'acces +

Donnees personnelles

+
RGPD — Article 15
+ + + + + + + +
Reference{{ attestation.reference }}Date{{ date|date('d/m/Y a H:i') }}Adresse IP{{ ip }}Sessions{{ data|length }}
+ + {% for entry in data %} +

Session #{{ loop.index }}

+
ID : {{ entry.visitor.id }} — Visite : {{ entry.visitor.createdAt|date('d/m/Y H:i') }}
+ {% if entry.events|length > 0 %} + + + + {% for event in entry.events %} + + {% endfor %} + +
DateTypePage
{{ event.createdAt|date('d/m/Y H:i:s') }}{{ event.type }}{{ event.page }}
+ {% else %} +
Aucun evenement enregistre.
+ {% endif %} + {% endfor %} + +
+ + + + + +
QR Code + Verifier ce document +

Scannez le QR code ou consultez le lien ci-dessous.

+ URL + {{ verify_url }} +
+
+
HMAC-SHA256 : {{ attestation.hmac }}
+
+

DPO (DPO-167945)

+ contact@e-cosplay.fr +
+
+
{% verbatim %}{{Sign;type=signature;width=150;height=50}}{% endverbatim %}
+
+
+ Association E-Cosplay — RNA W022006988 — SIREN 943121517
+ 42 rue de Saint-Quentin, 02800 Beautor, France — contact@e-cosplay.fr — 06 79 34 88 02
+ www.e-cosplay.fr — DPO : DPO-167945 +
+
+ + diff --git a/templates/pdf/rgpd_deletion.html.twig b/templates/pdf/rgpd_deletion.html.twig new file mode 100644 index 0000000..cff82f7 --- /dev/null +++ b/templates/pdf/rgpd_deletion.html.twig @@ -0,0 +1,93 @@ + + + + + + + + +
+ Suppression definitive +

Attestation de suppression des donnees

+
RGPD — Article 17
+ + + + + + +
Reference{{ attestation.reference }}Date{{ date|date('d/m/Y a H:i') }}Adresse IP{{ ip }}
+
+
Attestation de suppression
+
+ Action irreversible +

L'association E-Cosplay atteste par la presente que :

+

Toutes les donnees de navigation associees a l'adresse IP {{ ip }} ont ete definitivement supprimees de nos systemes le {{ date|date('d/m/Y') }} a {{ date|date('H:i') }}, conformement a l'article 17 du RGPD (Reglement UE 2016/679).

+

Donnees supprimees : identifiants de session, evenements de navigation, informations techniques.

+
+
+
Cette suppression est irreversible. Aucune donnee relative a cette adresse IP ne subsiste dans nos bases de donnees.
+
+ + + + + +
QR Code + Verifier ce document +

Scannez le QR code ou consultez le lien ci-dessous.

+ URL + {{ verify_url }} +
+
+
HMAC-SHA256 : {{ attestation.hmac }}
+
+

DPO (DPO-167945)

+ contact@e-cosplay.fr +
+
+
{% verbatim %}{{Sign;type=signature;width=150;height=50}}{% endverbatim %}
+
+
+ Association E-Cosplay — RNA W022006988 — SIREN 943121517
+ 42 rue de Saint-Quentin, 02800 Beautor, France — contact@e-cosplay.fr — 06 79 34 88 02
+ www.e-cosplay.fr — DPO : DPO-167945 +
+
+ + diff --git a/templates/pdf/rgpd_no_data.html.twig b/templates/pdf/rgpd_no_data.html.twig new file mode 100644 index 0000000..e63efc3 --- /dev/null +++ b/templates/pdf/rgpd_no_data.html.twig @@ -0,0 +1,92 @@ + + + + + + + + +
+ Document officiel +

Attestation d'absence de donnees

+
RGPD — Article 15
+ + + + + + +
Reference{{ attestation.reference }}Date{{ date|date('d/m/Y a H:i') }}Adresse IP{{ ip }}
+
+
Attestation
+
+

L'association E-Cosplay atteste par la presente que :

+

Aucune donnee de navigation associee a l'adresse IP {{ ip }} n'a ete trouvee dans nos systemes a la date du {{ date|date('d/m/Y') }} a {{ date|date('H:i') }}, conformement a l'article 15 du RGPD (Reglement UE 2016/679).

+
+
+
+

Aucune donnee personnelle (identifiants de session, evenements de navigation, informations techniques) n'est stockee dans nos bases de donnees pour cette adresse IP.

+
+
+ + + + + +
QR Code + Verifier ce document +

Scannez le QR code ou consultez le lien ci-dessous.

+ URL + {{ verify_url }} +
+
+
HMAC-SHA256 : {{ attestation.hmac }}
+
+

DPO (DPO-167945)

+ contact@e-cosplay.fr +
+
+
{% verbatim %}{{Sign;type=signature;width=150;height=50}}{% endverbatim %}
+
+
+ Association E-Cosplay — RNA W022006988 — SIREN 943121517
+ 42 rue de Saint-Quentin, 02800 Beautor, France — contact@e-cosplay.fr — 06 79 34 88 02
+ www.e-cosplay.fr — DPO : DPO-167945 +
+
+ + diff --git a/templates/security/2fa_email.html.twig b/templates/security/2fa_email.html.twig new file mode 100644 index 0000000..f04d45d --- /dev/null +++ b/templates/security/2fa_email.html.twig @@ -0,0 +1,54 @@ +{% extends 'base.html.twig' %} + +{% block title %}Verification par email - CRM Ecosplay{% endblock %} + +{% block header %} +
+
+ + CRM Ecosplay + +
+
+{% endblock %} + +{% block body %} +
+
+
+
+

Verification par email

+
+
+ {% if authenticationError %} +
+ {{ authenticationError|trans(authenticationErrorData, 'SchebTwoFactorBundle') }} +
+ {% endif %} + +

Un code a 6 chiffres a ete envoye a votre adresse email. Saisissez-le ci-dessous.

+ +
+
+ + +
+ + {% if isCsrfProtectionEnabled %} + + {% endif %} + + +
+ + Annuler et se deconnecter +
+
+
+
+{% endblock %} diff --git a/templates/security/2fa_google.html.twig b/templates/security/2fa_google.html.twig new file mode 100644 index 0000000..25fdaa4 --- /dev/null +++ b/templates/security/2fa_google.html.twig @@ -0,0 +1,61 @@ +{% extends 'base.html.twig' %} + +{% block title %}Verification 2FA - CRM Ecosplay{% endblock %} + +{% block header %} +
+
+ + CRM Ecosplay + +
+
+{% endblock %} + +{% block body %} +
+
+
+
+

Double authentification

+
+
+ {% if authenticationError %} +
+ {{ authenticationError|trans(authenticationErrorData, 'SchebTwoFactorBundle') }} +
+ {% endif %} + +

Ouvrez votre application d'authentification (Google Authenticator, Authy, etc.) et saisissez le code a 6 chiffres.

+ +
+
+ + +
+ + {% if displayTrustedOption %} + + {% endif %} + + {% if isCsrfProtectionEnabled %} + + {% endif %} + + +
+ + Annuler et se deconnecter +
+
+
+
+{% endblock %} diff --git a/templates/security/forgot_password.html.twig b/templates/security/forgot_password.html.twig new file mode 100644 index 0000000..7bc8346 --- /dev/null +++ b/templates/security/forgot_password.html.twig @@ -0,0 +1,80 @@ +{% extends 'base.html.twig' %} + +{% block title %}Mot de passe oublie - CRM Ecosplay{% endblock %} +{% block description %}Reinitialisation de votre mot de passe CRM Ecosplay.{% endblock %} + +{% block header %} +
+
+ + CRM Ecosplay + +
+
+{% endblock %} + +{% block body %} +
+
+
+
+

Mot de passe oublie

+
+ +
+ {% if error %} +
+ {{ error }} +
+ {% endif %} + + {% if success %} +
+ {{ success }} +
+ {% endif %} + + {% if step == 'email' %} +

Saisissez votre adresse email. Un code de verification a 6 chiffres vous sera envoye.

+
+ +
+ + +
+ +
+ {% elseif step == 'code' %} +

Saisissez le code a 6 chiffres recu par email ainsi que votre nouveau mot de passe.

+
+ +
+ + +
+
+ + +
+ +
+ {% endif %} + + Retour a la connexion +
+
+
+
+{% endblock %} diff --git a/templates/security/login.html.twig b/templates/security/login.html.twig new file mode 100644 index 0000000..1eedbdc --- /dev/null +++ b/templates/security/login.html.twig @@ -0,0 +1,53 @@ +{% extends 'base.html.twig' %} + +{% block title %}Connexion - CRM Ecosplay{% endblock %} +{% block description %}Connectez-vous au CRM Ecosplay.{% endblock %} + +{% block header %} +
+
+ + CRM Ecosplay + +
+
+{% endblock %} + +{% block body %} +
+
+ {% if error %} +
+ {{ error.messageKey|trans(error.messageData, 'security') }} +
+ {% endif %} + +
+
+
+
+ + +
+ +
+ + +
+ + + + +
+
+
+
+
+{% endblock %} diff --git a/templates/security/set_password.html.twig b/templates/security/set_password.html.twig new file mode 100644 index 0000000..dae0577 --- /dev/null +++ b/templates/security/set_password.html.twig @@ -0,0 +1,57 @@ +{% extends 'base.html.twig' %} + +{% block title %}Definir votre mot de passe - CRM Ecosplay{% endblock %} + +{% block header %} +
+
+ + CRM Ecosplay + +
+
+{% endblock %} + +{% block body %} +
+
+
+
+

Definir votre mot de passe

+

Bienvenue {{ user.firstName }} !

+
+ +
+ {% if error %} +
+ {{ error }} +
+ {% endif %} + +

Choisissez un mot de passe securise pour acceder a votre espace revendeur.

+ +
+
+ + +
+ +
+ + +
+ + +
+
+
+
+
+{% endblock %} diff --git a/templates/security/set_password_expired.html.twig b/templates/security/set_password_expired.html.twig new file mode 100644 index 0000000..7e142fb --- /dev/null +++ b/templates/security/set_password_expired.html.twig @@ -0,0 +1,30 @@ +{% extends 'base.html.twig' %} + +{% block title %}Lien expire - CRM Ecosplay{% endblock %} + +{% block header %} +
+
+ + CRM Ecosplay + +
+
+{% endblock %} + +{% block body %} +
+
+
+
+ +
+

Lien expire ou invalide

+

Ce lien de definition de mot de passe n'est plus valide. Il a peut-etre deja ete utilise ou a expire.

+ + Retour a l'accueil + +
+
+
+{% endblock %} diff --git a/templates/status/index.html.twig b/templates/status/index.html.twig new file mode 100644 index 0000000..9e75ab6 --- /dev/null +++ b/templates/status/index.html.twig @@ -0,0 +1,188 @@ +{% extends 'legal/_layout.html.twig' %} + +{% block title %}Status des services - E-Cosplay{% endblock %} +{% block description %}Etat en temps reel des services proposes par l'association E-Cosplay.{% endblock %} + +{% block body %} +
+ {# Banner global #} +
+
+ +

+ {% if globalStatus == 'up' %}Tous les systemes sont operationnels + {% elseif globalStatus == 'degraded' %}Performances degradees + {% elseif globalStatus == 'down' %}Incident en cours + {% else %}Maintenance en cours + {% endif %} +

+
+

Derniere verification : {{ "now"|date('d/m/Y a H:i') }}

+
+ + {# Messages globaux actifs #} + {% if globalMessages|length > 0 %} +
+ {% for msg in globalMessages %} +
+
+ {{ msg.severity }} + {{ msg.title }} + {{ msg.createdAt|date('d/m/Y H:i') }} +
+

{{ msg.content }}

+ {% if msg.author %} +

Par {{ msg.author.fullName }}

+ {% endif %} +
+ {% endfor %} +
+ {% endif %} + +
+ {% for data in servicesData %} +
+

{{ data.category.name }}

+ +
+ {% for item in data.services %} + {% set service = item.service %} + {% set uptime = item.uptime %} + {% set dailyStatus = item.dailyStatus %} + +
+ {# Header service #} +
+
+ + {{ service.name }} + {% if service.external %} + Externe + {% endif %} +
+
+ + {% if service.status == 'up' %}Operationnel + {% elseif service.status == 'degraded' %}Degrade + {% elseif service.status == 'down' %}Hors service + {% elseif service.status == 'maintenance' %}Maintenance + {% else %}Bientot + {% endif %} + + {{ uptime }}% +
+
+ + {# Message service #} + {% if service.message %} +
+ {{ service.message }} +
+ {% endif %} + + {# Messages actifs #} + {% for msg in item.messages %} +
+ {{ msg.title }} — {{ msg.content }} + {{ msg.createdAt|date('H:i') }} +
+ {% endfor %} + + {# Graphique 30 jours #} +
+ {% for day in dailyStatus %} +
+ {% endfor %} +
+
+ 30 jours + Aujourd'hui +
+ + {# Logs recents #} + {% if item.logs|length > 0 %} +
+

Historique (7j)

+ {% for log in item.logs %} +
+ {{ log.createdAt|date('d/m H:i') }} + {{ log.fromStatus }} + + {{ log.toStatus }} + ({{ log.source }}) +
+ {% endfor %} +
+ {% endif %} +
+ {% endfor %} +
+
+ {% else %} +

Aucun service configure.

+ {% endfor %} +
+ +
+

Page de status — Association E-Cosplay

+
+
+{% endblock %} diff --git a/vite.config.js b/vite.config.js new file mode 100644 index 0000000..141fc16 --- /dev/null +++ b/vite.config.js @@ -0,0 +1,99 @@ +// vite.config.js + +import { defineConfig } from 'vite'; +import { resolve } from 'path'; +import JavaScriptObfuscator from 'rollup-plugin-javascript-obfuscator'; +import tailwindcss from '@tailwindcss/vite' +import viteCompression from 'vite-plugin-compression'; + +export default defineConfig({ + + root: './assets', + base: '/assets/', + server: { + host: "0.0.0.0", + allowedHosts: ["esyweb.local"], + + port: 5173, + + open: false, + + cors: { + origin: ['https://esyweb.local'] + }, + watch: { + usePolling: true, + interval: 1000, + }, + }, + resolve: { + alias: { + '@': resolve(__dirname, 'assets'), + }, + // Extensions de fichiers à résoudre automatiquement + extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue', '.scss', '.css'], + }, + build: { + outDir: resolve(__dirname, 'public/build'), + assetsDir: '', + emptyOutDir: true, + manifest: true, + sourcemap: false, + // Minification par défaut : esbuild est déjà très rapide et efficace + minify: 'esbuild', + cssMinify: 'esbuild', + // NOUVEAU : Stratégie de découpage du code pour améliorer le cache client + rollupOptions: { + input: { + app: resolve(__dirname, 'assets/app.js'), + } + }, + }, + + // --- Plugins Vite --- + plugins: [ + tailwindcss(), + // NOUVEAU: Ajoutez Preact/React/Vue si vous utilisez ces frameworks + // preact(), + + // 1. Compression des Assets (Gzip et Brotli) + // Crée des fichiers .gz et .br à côté des assets originaux. + // Votre serveur web (Nginx/Apache) doit être configuré pour servir ces versions compressées. + viteCompression({ + verbose: true, + disable: false, + threshold: 10240, // Compresse seulement les fichiers > 10KB + algorithm: 'gzip', + ext: '.gz', + }), + viteCompression({ + verbose: true, + disable: false, + threshold: 10240, + algorithm: 'brotliCompress', // Brotli est plus efficace que Gzip + ext: '.br', + // Ne génère le brotli que si l'algorithme est disponible (par défaut) + deleteOriginFile: false // Important : gardez les fichiers originaux + }), + + JavaScriptObfuscator({ + // Options recommandées pour un bon équilibre entre sécurité et performance + compact: true, + sourceMap: false, + controlFlowFlattening: true, // Très efficace mais coûteux en performance à l'exécution + deadCodeInjection: false, + debugProtection: false, + identifierNamesGenerator: 'hexadecimal', // Rend le code illisible + log: false, + numbersToExpressions: true, + simplify: true, + splitStrings: true, + stringArray: true, + stringArrayThreshold: 0.75, + transformObjectKeys: true, + unicodeEscapeSequence: false, + }), + + ], + define: {}, +}); diff --git a/vitest.config.js b/vitest.config.js new file mode 100644 index 0000000..4f2adcc --- /dev/null +++ b/vitest.config.js @@ -0,0 +1,14 @@ +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + environment: 'happy-dom', + include: ['tests/js/**/*.test.js'], + coverage: { + provider: 'v8', + include: ['assets/**/*.js'], + exclude: ['assets/modules/event-map.js', 'assets/modules/billet-designer.js', 'assets/modules/stripe-payment.js'], + reporter: ['text', 'lcov'], + }, + }, +})