Full Code of tchapi/davis for AI

main 82d577822192 cached
174 files
746.3 KB
189.5k tokens
522 symbols
1 requests
Download .txt
Showing preview only (798K chars total). Download the full file or copy to clipboard to get everything.
Repository: tchapi/davis
Branch: main
Commit: 82d577822192
Files: 174
Total size: 746.3 KB

Directory structure:
gitextract_80g_njsc/

├── .dockerignore
├── .github/
│   ├── FUNDING.yml
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.yml
│   │   └── feature_request.yml
│   └── workflows/
│       ├── ci.yml
│       └── main.yml
├── .gitignore
├── .hadolint.yaml
├── .php-cs-fixer.php
├── LICENSE
├── README.md
├── bin/
│   ├── console
│   └── phpunit
├── composer.json
├── config/
│   ├── bundles.php
│   ├── packages/
│   │   ├── cache.yaml
│   │   ├── dev/
│   │   │   ├── debug.yaml
│   │   │   ├── monolog.yaml
│   │   │   └── web_profiler.yaml
│   │   ├── doctrine.yaml
│   │   ├── doctrine_migrations.yaml
│   │   ├── framework.yaml
│   │   ├── mailer.yaml
│   │   ├── prod/
│   │   │   ├── deprecations.yaml
│   │   │   ├── doctrine.yaml
│   │   │   ├── monolog.yaml
│   │   │   └── routing.yaml
│   │   ├── routing.yaml
│   │   ├── security.yaml
│   │   ├── test/
│   │   │   ├── framework.yaml
│   │   │   ├── monolog.yaml
│   │   │   ├── twig.yaml
│   │   │   ├── validator.yaml
│   │   │   └── web_profiler.yaml
│   │   ├── translation.yaml
│   │   ├── twig.yaml
│   │   └── validator.yaml
│   ├── reference.php
│   ├── routes/
│   │   ├── attributes.yaml
│   │   └── dev/
│   │       ├── framework.yaml
│   │       └── web_profiler.yaml
│   ├── routes.yaml
│   └── services.yaml
├── docker/
│   ├── Dockerfile
│   ├── Dockerfile-standalone
│   ├── configurations/
│   │   ├── Caddyfile
│   │   ├── nginx.conf
│   │   ├── opcache.ini
│   │   └── supervisord.conf
│   ├── docker-compose-postgresql.yml
│   ├── docker-compose-sqlite.yml
│   ├── docker-compose-standalone.yml
│   └── docker-compose.yml
├── docs/
│   └── api/
│       ├── README.md
│       └── v1/
│           ├── calendars/
│           │   ├── all.md
│           │   ├── create.md
│           │   ├── delete.md
│           │   ├── details.md
│           │   ├── edit.md
│           │   ├── share_add.md
│           │   ├── share_remove.md
│           │   └── shares.md
│           ├── health.md
│           └── users/
│               ├── all.md
│               └── details.md
├── migrations/
│   ├── Version20191030113307.php
│   ├── Version20191113170650.php
│   ├── Version20191125093508.php
│   ├── Version20191202091507.php
│   ├── Version20191203111729.php
│   ├── Version20210928132307.php
│   ├── Version20221106220411.php
│   ├── Version20221106220412.php
│   ├── Version20221211154443.php
│   ├── Version20230209142217.php
│   ├── Version20231001214111.php
│   ├── Version20231001214112.php
│   ├── Version20231001214113.php
│   ├── Version20231229203515.php
│   ├── Version20250409193948.php
│   ├── Version20250421163214.php
│   └── Version20260131161930.php
├── phpunit.xml.dist
├── public/
│   ├── .htaccess
│   ├── css/
│   │   └── style.css
│   ├── index.php
│   ├── js/
│   │   ├── app.js
│   │   └── color.mode.toggler.js
│   ├── robots.txt
│   └── site.webmanifest
├── src/
│   ├── Command/
│   │   ├── ApiGenerateCommand.php
│   │   └── SyncBirthdayCalendars.php
│   ├── Constants.php
│   ├── Controller/
│   │   ├── Admin/
│   │   │   ├── AddressBookController.php
│   │   │   ├── CalendarController.php
│   │   │   ├── DashboardController.php
│   │   │   └── UserController.php
│   │   ├── Api/
│   │   │   └── ApiController.php
│   │   ├── DAVController.php
│   │   └── SecurityController.php
│   ├── DataFixtures/
│   │   └── AppFixtures.php
│   ├── Entity/
│   │   ├── AddressBook.php
│   │   ├── AddressBookChange.php
│   │   ├── Calendar.php
│   │   ├── CalendarChange.php
│   │   ├── CalendarInstance.php
│   │   ├── CalendarObject.php
│   │   ├── CalendarSubscription.php
│   │   ├── Card.php
│   │   ├── Lock.php
│   │   ├── Principal.php
│   │   ├── PropertyStorage.php
│   │   ├── SchedulingObject.php
│   │   └── User.php
│   ├── Form/
│   │   ├── AddressBookType.php
│   │   ├── CalendarInstanceType.php
│   │   └── UserType.php
│   ├── Kernel.php
│   ├── Logging/
│   │   └── Monolog/
│   │       └── PasswordFilterProcessor.php
│   ├── Plugins/
│   │   ├── BirthdayCalendarPlugin.php
│   │   ├── DavisIMipPlugin.php
│   │   └── PublicAwareDAVACLPlugin.php
│   ├── Repository/
│   │   ├── CalendarInstanceRepository.php
│   │   └── PrincipalRepository.php
│   ├── Security/
│   │   ├── AdminUser.php
│   │   ├── AdminUserProvider.php
│   │   ├── ApiKeyAuthenticator.php
│   │   └── LoginFormAuthenticator.php
│   ├── Services/
│   │   ├── BasicAuth.php
│   │   ├── BirthdayService.php
│   │   ├── IMAPAuth.php
│   │   ├── LDAPAuth.php
│   │   └── Utils.php
│   └── Version.php
├── templates/
│   ├── _partials/
│   │   ├── add_delegate_modal.html.twig
│   │   ├── back_button.html.twig
│   │   ├── delegate_row.html.twig
│   │   ├── delete_modal.html.twig
│   │   ├── flashes.html.twig
│   │   ├── navigation.html.twig
│   │   └── share_modal.html.twig
│   ├── addressbooks/
│   │   ├── edit.html.twig
│   │   └── index.html.twig
│   ├── base.html.twig
│   ├── calendars/
│   │   ├── edit.html.twig
│   │   └── index.html.twig
│   ├── dashboard.html.twig
│   ├── index.html.twig
│   ├── mails/
│   │   ├── scheduling.html.twig
│   │   └── scheduling.txt.twig
│   ├── security/
│   │   └── login.html.twig
│   └── users/
│       ├── delegates.html.twig
│       ├── edit.html.twig
│       └── index.html.twig
├── tests/
│   ├── .gitignore
│   ├── Functional/
│   │   ├── Commands/
│   │   │   └── SyncBirthdayCalendarTest.php
│   │   ├── Controllers/
│   │   │   ├── AddressBookControllerTest.php
│   │   │   ├── ApiControllerTest.php
│   │   │   ├── CalendarControllerTest.php
│   │   │   ├── DashboardTest.php
│   │   │   └── UserControllerTest.php
│   │   ├── DavTest.php
│   │   └── Service/
│   │       └── BirthdayServiceTest.php
│   └── bootstrap.php
└── translations/
    ├── .gitignore
    ├── messages+intl-icu.de.xlf
    ├── messages+intl-icu.en.xlf
    ├── messages+intl-icu.fr.xliff
    ├── security.de.xlf
    ├── security.en.xlf
    ├── security.fr.xlf
    ├── validators.de.xlf
    ├── validators.en.xlf
    └── validators.fr.xlf

================================================
FILE CONTENTS
================================================

================================================
FILE: .dockerignore
================================================
_screenshots
.DS_Store
README.md
LICENSE
.git
.gitignore
.github
.env.local
.env.test.local
phpunit.xml.dist
.php-cs*
.phpunit.*
.dockerignore
var/cache/*
var/log/*


================================================
FILE: .github/FUNDING.yml
================================================
custom: ['https://www.paypal.me/tchap']


================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.yml
================================================
name: Bug Report
description: Something is not working as expected
labels: ["bug"]
body:
  - type: markdown
    attributes:
      value: |
        Before opening an issue, please:
        - Check `./var/log/prod.log` for errors (only written when errors occur, thanks to the `fingers_crossed` filter)
        - Check your web server / reverse proxy logs
        - Read the [Troubleshooting section of the README](https://github.com/tchapi/davis/blob/main/README.md#troubleshooting)

  - type: input
    id: davis_version
    attributes:
      label: Davis version
      description: Tag or commit SHA. Found on the `/` status page or via `git describe --tags`.
      placeholder: "e.g. v5.1.0"
    validations:
      required: true

  - type: dropdown
    id: install_method
    attributes:
      label: Installation method
      options:
        - Docker – standalone (with Caddy)
        - Docker – barebone (no reverse proxy)
        - Bare metal / manual
        - NixOS module
        - Other (describe below)
    validations:
      required: true

  - type: input
    id: php_version
    attributes:
      label: PHP version (bare metal only)
      description: Output of `php --version`. Skip if using Docker.
      placeholder: "e.g. 8.2.18"

  - type: dropdown
    id: database
    attributes:
      label: Database
      options:
        - MySQL
        - MariaDB
        - PostgreSQL
        - SQLite
        - Other
    validations:
      required: true

  - type: dropdown
    id: reverse_proxy
    attributes:
      label: Reverse proxy
      options:
        - None (standalone Docker with Caddy)
        - Nginx
        - Apache
        - Caddy
        - Traefik
        - Other
    validations:
      required: true

  - type: dropdown
    id: auth_method
    attributes:
      label: Authentication method
      options:
        - Internal (ADMIN_LOGIN / ADMIN_PASSWORD)
        - IMAP
        - LDAP
    validations:
      required: true

  - type: checkboxes
    id: protocols
    attributes:
      label: Affected protocol(s)
      options:
        - label: CalDAV
        - label: CardDAV
        - label: WebDAV
        - label: Admin dashboard
        - label: API

  - type: textarea
    id: client
    attributes:
      label: Client(s) exhibiting the issue
      description: Name, version, OS/platform. Add multiple if relevant.
      placeholder: |
        - DAVx⁵ 4.3.14 on Android 14
        - Apple Calendar on macOS 14.4
        - Thunderbird 115.10 on Ubuntu 22.04
    validations:
      required: true

  - type: textarea
    id: env_vars
    attributes:
      label: Relevant environment variables
      description: |
        Sanitize secrets (APP_SECRET, DATABASE_URL password, API_KEY, etc.).
        Include at minimum: APP_ENV, CALDAV_ENABLED, CARDDAV_ENABLED, WEBDAV_ENABLED, AUTH_METHOD, and any env vars you think are relevant.
      render: shell
      placeholder: |
        APP_ENV=prod
        AUTH_METHOD=...
        CALDAV_ENABLED=true
        CARDDAV_ENABLED=true
        WEBDAV_ENABLED=false
        DATABASE_URL=mysql://user:***@host:3306/davis
    validations:
      required: true

  - type: textarea
    id: steps
    attributes:
      label: Steps to reproduce
      placeholder: |
        1. Configure client with URL https://dav.example.com/dav
        2. ...
        3. Observe error
    validations:
      required: true

  - type: textarea
    id: expected
    attributes:
      label: Expected behaviour
    validations:
      required: true

  - type: textarea
    id: actual
    attributes:
      label: Actual behaviour
      description: Include any error messages shown in the UI or client.
    validations:
      required: true

  - type: textarea
    id: logs
    attributes:
      label: Logs
      description: |
        Paste the relevant section of `./var/log/prod.log` (relative to your Davis installation root, **not** `/var/log`).
        Also include web server / reverse proxy error logs if applicable.
        Set `APP_ENV=dev` temporarily to get verbose output if `prod.log` is empty (you need to install dev dependencies with composer).
      render: text

  - type: textarea
    id: additional
    attributes:
      label: Additional context
      description: Anything else that might be relevant (docker-compose snippet, nginx config excerpt, network topology, etc.).

================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.yml
================================================
name: Feature Request
description: Suggest an improvement or new functionality
labels: ["feature request"]
body:
  - type: markdown
    attributes:
      value: |
        Please check [existing issues](https://github.com/tchapi/davis/issues) and the [Davis roadmap](https://github.com/users/tchapi/projects/1) first to avoid duplicates.

  - type: textarea
    id: problem
    attributes:
      label: Problem / motivation
      description: What are you trying to do, and why is it currently not possible or inconvenient?
      placeholder: "e.g. I cannot do X, which forces me to..."
    validations:
      required: true

  - type: textarea
    id: solution
    attributes:
      label: Proposed solution
      description: Describe the feature you have in mind. Be as specific as possible.
    validations:
      required: true

  - type: textarea
    id: alternatives
    attributes:
      label: Alternatives considered
      description: Other approaches you have thought of or tried, and why they fall short.

  - type: dropdown
    id: area
    attributes:
      label: Area
      options:
        - CalDAV
        - CardDAV
        - WebDAV
        - Admin dashboard
        - API
        - Authentication (IMAP / LDAP / internal)
        - Docker / deployment
        - Documentation
        - Other
    validations:
      required: true

  - type: textarea
    id: additional
    attributes:
      label: Additional context
      description: Links, screenshots, references to relevant RFCs or client behaviour, etc.

================================================
FILE: .github/workflows/ci.yml
================================================
name: CI

on:
  push:
    branches:
      - main  # Only run on pushes to main
  pull_request:
    # Run on all PRs (no path restrictions)

env:
  COMPOSER_ALLOW_SUPERUSER: '1'
  SYMFONY_DEPRECATIONS_HELPER: max[self]=0
  ADMIN_LOGIN: admin
  ADMIN_PASSWORD: test

jobs:
  dockerfile-checks:
    name: Dockerfile Checks
    runs-on: ubuntu-latest
    strategy:
      matrix:
        dockerfile:
          - docker/Dockerfile
          - docker/Dockerfile-standalone
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Lint with Hadolint
        uses: hadolint/hadolint-action@v3.1.0
        with:
          dockerfile: ${{ matrix.dockerfile }}
          failure-threshold: warning

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Validate Dockerfile syntax
        run: |
          docker buildx build \
            --file ${{ matrix.dockerfile }} \
            --platform linux/amd64 \
            --check \
            .

  analyze:
    name: Analyze
    runs-on: ubuntu-latest
    container:
      image: php:8.4-alpine
      options: >-
        --tmpfs /tmp:exec
        --tmpfs /var/tmp:exec
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Install GD / ZIP PHP extension
        run: |
          apk add $PHPIZE_DEPS libpng-dev libzip-dev
          docker-php-ext-configure gd
          docker-php-ext-configure zip
          docker-php-ext-install gd zip

      - name: Install Composer
        run: wget -qO - https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer --quiet

      - name: Validate Composer
        run: composer validate

      - name: Update to highest dependencies with Composer
        run: composer install --no-interaction --no-progress --ansi

      - name: Analyze
        run: vendor/bin/php-cs-fixer fix --ansi

  phpunit:
    name: PHPUnit (PHP ${{ matrix.php }})
    runs-on: ubuntu-latest
    container:
      image: php:${{ matrix.php }}-alpine
      options: >-
        --tmpfs /tmp:exec
        --tmpfs /var/tmp:exec
    services:
      mysql:
        image: mariadb:10.11
        env:
          # Corresponds to what is in .env.test
          MYSQL_DATABASE: davis_test
          MYSQL_USER: davis
          MYSQL_PASSWORD: davis
          MYSQL_ROOT_PASSWORD: root
        options: >-
          --health-cmd "mysqladmin ping"
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
        ports:
          - 3306:3306

    strategy:
      matrix:
        php:
          - '8.2'
          - '8.3'
          - '8.4'
          - '8.5'
      fail-fast: false

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Install MySQL / GD / ZIP PHP extensions
        run: |
          apk add $PHPIZE_DEPS icu-libs icu-dev libpng-dev libzip-dev
          docker-php-ext-configure intl
          docker-php-ext-configure gd
          docker-php-ext-configure zip
          docker-php-ext-install pdo pdo_mysql intl gd zip

      - name: Install Composer
        run: wget -qO - https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer --quiet

      - name: Install dependencies with Composer
        run: composer install --no-progress --no-interaction --ansi

      - name: Run tests with PHPUnit
        env:
          DATABASE_URL: mysql://davis:davis@mysql:3306/davis_test
        run: vendor/bin/phpunit --process-isolation --colors=always

  migrations:
    name: Migrations (${{ matrix.database }})
    runs-on: ubuntu-latest
    container:
      image: php:8.4-alpine
      options: >-
        --tmpfs /tmp:exec
        --tmpfs /var/tmp:exec

    services:
      mysql:
        image: mariadb:10.11
        env:
          MYSQL_DATABASE: davis_test
          MYSQL_USER: davis
          MYSQL_PASSWORD: davis
          MYSQL_ROOT_PASSWORD: root
        options: >-
          --health-cmd "mysqladmin ping"
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5

      postgres:
        image: postgres:15-alpine
        env:
          POSTGRES_DB: davis_test
          POSTGRES_USER: davis
          POSTGRES_PASSWORD: davis
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5

    strategy:
      matrix:
        database:
          - mysql
          - postgresql
          - sqlite
      fail-fast: false

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Install database extensions
        run: |
          apk add $PHPIZE_DEPS icu-libs icu-dev libpng-dev libzip-dev postgresql-dev sqlite-dev
          docker-php-ext-configure intl
          docker-php-ext-configure gd
          docker-php-ext-configure zip
          docker-php-ext-install pdo pdo_mysql pdo_pgsql pdo_sqlite intl gd zip

      - name: Install Composer
        run: wget -qO - https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer --quiet

      - name: Install dependencies with Composer
        run: composer install --no-progress --no-interaction --ansi

      - name: Run migrations (MySQL)
        if: matrix.database == 'mysql'
        env:
          DATABASE_URL: mysql://davis:davis@mysql:3306/davis_test
        run: |
          php bin/console doctrine:database:create --if-not-exists --env=test
          php bin/console doctrine:migrations:migrate --no-interaction --env=test
          php bin/console doctrine:schema:validate --env=test

      - name: Run migrations (PostgreSQL)
        if: matrix.database == 'postgresql'
        env:
          DATABASE_URL: postgresql://davis:davis@postgres:5432/davis_test?serverVersion=15&charset=utf8
        run: |
          php bin/console doctrine:database:create --if-not-exists --env=test
          php bin/console doctrine:migrations:migrate --no-interaction --env=test
          php bin/console doctrine:schema:validate --skip-sync --env=test

      - name: Run migrations (SQLite)
        if: matrix.database == 'sqlite'
        env:
          DATABASE_URL: sqlite:///%kernel.project_dir%/var/data_test.db
        run: |
          php bin/console doctrine:migrations:migrate --no-interaction --env=test
          php bin/console doctrine:schema:validate --skip-sync --env=test

  smoke-test:
    name: Application Smoke Test
    runs-on: ubuntu-latest
    container:
      image: php:8.4-alpine
      options: >-
        --tmpfs /tmp:exec
        --tmpfs /var/tmp:exec

    services:
      mysql:
        image: mariadb:10.11
        env:
          MYSQL_DATABASE: davis_test
          MYSQL_USER: davis
          MYSQL_PASSWORD: davis
          MYSQL_ROOT_PASSWORD: root
        options: >-
          --health-cmd "mysqladmin ping"
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Install extensions and curl
        run: |
          apk add $PHPIZE_DEPS icu-libs icu-dev libpng-dev libzip-dev curl
          docker-php-ext-configure intl
          docker-php-ext-configure gd
          docker-php-ext-configure zip
          docker-php-ext-install pdo pdo_mysql intl gd zip

      - name: Install Composer
        run: wget -qO - https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer --quiet

      - name: Install dependencies with Composer
        run: composer install --no-progress --no-interaction --ansi --optimize-autoloader

      - name: Prepare application
        env:
          DATABASE_URL: mysql://davis:davis@mysql:3306/davis_test
          APP_ENV: test
        run: |
          php bin/console doctrine:database:create --if-not-exists --env=test
          php bin/console doctrine:migrations:migrate --no-interaction --env=test
          php bin/console cache:clear --env=test

      - name: Start Symfony server in background
        env:
          DATABASE_URL: mysql://davis:davis@mysql:3306/davis_test
          APP_ENV: test
        run: |
          php -S 127.0.0.1:8000 -t public/ &
          echo $! > server.pid
          sleep 3

      - name: Test application responds
        run: |
          # Test that the app responds with a successful HTTP status
          RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:8000/)
          echo "HTTP Response code: $RESPONSE"
          if [ "$RESPONSE" -ge 200 ] && [ "$RESPONSE" -lt 400 ]; then
            echo "✅ Application is responding correctly"
          else
            echo "❌ Application returned unexpected status code: $RESPONSE"
            exit 1
          fi

      - name: Test dashboard
        continue-on-error: true
        run: |
          curl -f http://127.0.0.1:8000/dashboard || echo "No health endpoint available"

      - name: Stop server
        if: always()
        run: |
          if [ -f server.pid ]; then
            kill $(cat server.pid) || true
          fi


================================================
FILE: .github/workflows/main.yml
================================================
name: Publish Docker image

on:
  workflow_dispatch:
  release:
    types: [published]

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

env:
  # Use docker.io for Docker Hub if empty
  REGISTRY: ghcr.io
  # github.repository as <account>/<repo>
  ACCOUNT: tchapi

jobs:
  build:
    name: Build Docker images
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        image:
          - davis
          - davis-standalone
        platform:
          - linux/amd64
          - linux/arm64
        include:
          - image: davis
            dockerfile: docker/Dockerfile
          - image: davis-standalone
            dockerfile: docker/Dockerfile-standalone
    
    steps:
      - name: Prepare
        run: |
          platform=${{ matrix.platform }}
          echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
      
      - name: Checkout
        uses: actions/checkout@v4
      
      - name: Extract metadata
        id: meta
        uses: docker/metadata-action@v5
        with:
          context: git
          images: ${{ env.REGISTRY }}/${{ env.ACCOUNT }}/${{ matrix.image }}
          tags: type=raw,value=
      
      - name: Set up QEMU
        if: matrix.platform == 'linux/arm64'
        uses: docker/setup-qemu-action@v3
      
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3
        with:
          version: latest
      
      - name: Login to GitHub Container Registry
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      
      - name: Build and Push by digest
        id: build
        uses: docker/build-push-action@v6
        with:
          context: .
          file: ${{ matrix.dockerfile }}
          platforms: ${{ matrix.platform }}
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          annotations: ${{ steps.meta.outputs.annotations }}
          outputs: type=image,name=${{ env.REGISTRY }}/${{ env.ACCOUNT }}/${{ matrix.image }},push-by-digest=true,name-canonical=true,push=true
          cache-from: type=gha,scope=${{ matrix.image }}-${{ env.PLATFORM_PAIR }}
          cache-to: type=gha,mode=max,scope=${{ matrix.image }}-${{ env.PLATFORM_PAIR }}
      
      - name: Export digest
        run: |
          mkdir -p /tmp/digests/
          digest="${{ steps.build.outputs.digest }}"
          touch "/tmp/digests/${digest#sha256:}"
      
      - name: Upload digest
        uses: actions/upload-artifact@v4
        with:
          name: digests-${{ matrix.image }}_${{ env.PLATFORM_PAIR }}
          path: /tmp/digests/*
          if-no-files-found: error
          retention-days: 1
      
      - name: Build summary
        run: |
          echo "### ✅ Build Complete" >> $GITHUB_STEP_SUMMARY
          echo "- **Image**: \`${{ matrix.image }}\`" >> $GITHUB_STEP_SUMMARY
          echo "- **Platform**: \`${{ matrix.platform }}\`" >> $GITHUB_STEP_SUMMARY
          echo "- **Digest**: \`${{ steps.build.outputs.digest }}\`" >> $GITHUB_STEP_SUMMARY

  merge:
    name: Create merged manifest
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        image:
          - davis
          - davis-standalone
    needs: build
    
    steps:
      - name: Checkout
        uses: actions/checkout@v4
      
      - name: Download digests
        uses: actions/download-artifact@v4
        with:
          path: /tmp/digests/${{ matrix.image }}/
          pattern: digests-${{ matrix.image }}_*
          merge-multiple: true
      
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3
        with:
          version: latest
      
      - name: Docker meta
        id: meta
        uses: docker/metadata-action@v5
        with:
          context: git
          images: ${{ env.REGISTRY }}/${{ env.ACCOUNT }}/${{ matrix.image }}
          tags: |
            type=semver,pattern={{version}}
            type=edge,branch=${{ github.ref_name }}
      
      - name: Login to GitHub Container Registry
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      
      - name: Create manifest list and push
        working-directory: /tmp/digests/${{ matrix.image }}/
        run: |
          docker buildx imagetools create \
            $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
            --annotation index:org.opencontainers.image.created="${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.created'] }}" \
            --annotation index:org.opencontainers.image.description="${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.description'] }}" \
            --annotation index:org.opencontainers.image.version="${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.version'] }}" \
            --annotation index:org.opencontainers.image.licenses="${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.licenses'] }}" \
            --annotation index:org.opencontainers.image.title="${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.title'] }}" \
            --annotation index:org.opencontainers.image.source="${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.source'] }}" \
            --annotation index:org.opencontainers.image.url="${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.url'] }}" \
            --annotation index:org.opencontainers.image.revision="${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.revision'] }}" \
            $(printf '${{ env.REGISTRY }}/${{ env.ACCOUNT }}/${{ matrix.image }}@sha256:%s ' *)
      
      - name: Inspect image
        run: |
          docker buildx imagetools inspect \
            ${{ env.REGISTRY }}/${{ env.ACCOUNT }}/${{ matrix.image }}:${{ steps.meta.outputs.version }}

================================================
FILE: .gitignore
================================================
###> symfony/framework-bundle ###
/.env.local
/.env.local.php
/.env.*.local
/config/secrets/prod/prod.decrypt.private.php
/public/bundles/
/var/
/vendor/
###< symfony/framework-bundle ###

###> symfony/phpunit-bridge ###
.phpunit
.phpunit.result.cache
/phpunit.xml
###< symfony/phpunit-bridge ###
###> friendsofphp/php-cs-fixer ###
/.php_cs.cache
###< friendsofphp/php-cs-fixer ###

.DS_Store
TODO.todo
webdav_*

================================================
FILE: .hadolint.yaml
================================================
ignored:
  - DL3018  # We don't pin apk versions (Alpine is rolling)
failure-threshold: error  # Only fail on errors, not warnings

================================================
FILE: .php-cs-fixer.php
================================================
<?php

$finder = (new PhpCsFixer\Finder())
    ->in(__DIR__)
    ->exclude('var')
;

return (new PhpCsFixer\Config())
    ->setRules([
        '@Symfony' => true,
        'ordered_imports' => true,                      // Order "use" alphabetically
        'array_syntax' => ['syntax' => 'short'],        // Replace array() by []
        'no_useless_return' => true,                    // Keep return null;
        'phpdoc_order' => true,                         // Clean up the /** php doc */
        'linebreak_after_opening_tag' => true,
        'multiline_whitespace_before_semicolons' => false,
        'phpdoc_add_missing_param_annotation' => true,
        'single_trait_insert_per_statement' => false
    ])
    ->setUnsupportedPhpVersionAllowed(true)
    ->setUsingCache(false)
    ->setFinder($finder)
;


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2019 tchap

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
Davis
---

[![Build Status][ci_badge]][ci_link]
[![Publish Docker image](https://github.com/tchapi/davis/actions/workflows/main.yml/badge.svg?branch=main)](https://github.com/tchapi/davis/actions/workflows/main.yml)
[![Latest release][release_badge]][release_link]
[![License](https://img.shields.io/github/license/tchapi/davis)](https://github.com/tchapi/davis/blob/main/LICENSE)
![Platform](https://img.shields.io/badge/platform-amd64%20%7C%20arm64-blue?logo=docker)
![PHP Version](https://img.shields.io/badge/php-8.2%20%7C%208.3%20%7C%208.4-777BB4?logo=php&logoColor=white)
[![Sponsor me][sponsor_badge]][sponsor_link]

A modern, simple, feature-packed, fully translatable DAV server, admin interface and frontend based on `sabre/dav`, built with [Symfony 7](https://symfony.com/) and [Bootstrap 5](https://getbootstrap.com/), initially inspired by [Baïkal](https://github.com/sabre-io/Baikal) (_see dependencies table below for more detail_)

### Web admin dashboard

Provides user edition, calendar creation and sharing, and address book creation. The interface is simple and straightforward, responsive, and provides a light and a dark mode.

Supports **Basic authentication**, as well as **IMAP** and **LDAP** (_via external providers_).

### DAV Server

The underlying server implementation supports (*non-exhaustive list*) CalDAV, CardDAV, WebDAV, calendar sharing, scheduling, mail notifications, and server-side subscriptions (*depending on the capabilities of the client*).

### Additional features ✨

- Subscriptions (to be added via the client, such as the macOS calendar, for instance)
- Public calendars, available to anyone with the link
- Automatic birthday calendar, updated on the fly when birthdates change in your contacts

### Deployment

Easily containerisable (_`Dockerfile` and sample `docker-compose` configuration file provided_).

NixOS [package](https://search.nixos.org/packages?channel=unstable&show=davis&from=0&size=50&sort=relevance&type=packages&query=davis) and module available.

Comes with already built Docker images in two flavours: [standalone](https://github.com/tchapi/davis/pkgs/container/davis-standalone) (with included Caddy reverse proxy) or [barebone](https://github.com/tchapi/davis/pkgs/container/davis).

- - -

✨ Created and maintained (with the help of the community) by [@tchapi](https://github.com/tchapi). ✨

![Dashboard page](_screenshots/dashboard.png)
![User creation page](_screenshots/user.png)
![Sharing page](_screenshots/sharing.png)

| Dark / Light mode  | Useful information at hand        |
|--------------------|----------------------------|
| ![Color mode](_screenshots/mode.png)| ![Setup information](_screenshots/setup_info.png)|

# 🔩 Requirements

  - PHP > 8.2 (with `pdo_mysql` [or `pdo_pgsql`, `pdo_sqlite`], `gd` and `intl` extensions), compatible up to PHP 8.5 (_See dependencies table below_)
  - A compatible database layer, such as MySQL or MariaDB (recommended), PostgreSQL (not extensively tested yet) or SQLite (not extensively tested yet)
  - Composer > 2 (_The last release compatible with Composer 1 is [v1.6.2](https://github.com/tchapi/davis/releases/tag/v1.6.2)_)
  - The [`imap`](https://www.php.net/manual/en/imap.installation.php) and [`ldap`](https://www.php.net/manual/en/ldap.installation.php) PHP extensions if you want to use either authentication methods (_these are not enabled / compiled by default except in the Docker image_)

Dependencies
------------

| Release            | Status                     | PHP version        |
|--------------------|----------------------------|--------------------|
| `main` (edge)      | development branch         | PHP 8.2+           |
| `v5.x`             | stable                     | PHP 8.2+           |
| `v4.x`             | security fixes only        | PHP 8.0 → 8.3      |
| `v3.x`             | :warning: unmaintained     | PHP 7.3 → 8.2      |

# 🧰 Installation

0. Clone this repository

1. Retrieve the dependencies:

    a. If you plan to run Davis locally, for development purposes

    ```
    composer install
    ```

    b. If you plan to run Davis on production

    ```
    composer install --no-dev
    ```

   And set `APP_ENV=prod` in your `.env.local` file (see below)


3. At least put the correct credentials to your database (driver and url) in your `.env.local` file so you can easily create the necessary tables.

4. Run the migrations to create all the necessary tables:

    ```
    bin/console doctrine:migrations:migrate
    ```

**Davis** can also be used with a pre-existing MySQL database (_for instance, one previously managed by Baïkal_). See the paragraph "Migrating from Baikal" for more info.

> [!NOTE]
>
> The tables are not _exactly_ equivalent to those of Baïkal, and allow for a bit more room in columns for instance (among other things)

## Configuration

Create your own `.env.local` file to change the necessary variables, if you plan on using `symfony/dotenv`.

> [!NOTE]
>
> If your installation is behind a web server like Apache or Nginx, you can setup the env vars directly in your Apache or Nginx configuration (see below). Skip this part in this case.

> [!CAUTION]
>
> In a production environnement, the `APP_ENV` variable MUST be set to `prod` to prevent leaking sensitive data.

**a. The database driver and url** (_you should already have it configured since you created the database previously_)

```shell
DATABASE_DRIVER=mysql # or postgresql, or sqlite
DATABASE_URL=mysql://db_user:db_pass@host:3306/db_name?serverVersion=10.9.3-MariaDB&charset=utf8mb4
```

**b. The admin password for the backend**

```shell
ADMIN_LOGIN=admin
ADMIN_PASSWORD=test
```

> [!NOTE]
>
> You can bypass auth entirely if you use a third party authorization provider such as Authelia. In that case, set the `ADMIN_AUTH_BYPASS` env var to `true` (case-sensitive, this is actually the string `true`, not a boolean) to allow full access to the dashboard. This does not change the behaviour of the DAV server.

**c. The auth Realm and method for HTTP auth**

```shell
AUTH_REALM=SabreDAV
AUTH_METHOD=Basic # can be "Basic", "IMAP" or "LDAP"
```
> See [the following paragraph](#specific-environment-variables-for-imap-and-ldap-authentication-methods) for more information if you choose either IMAP or LDAP.

**d. The global flags to enable CalDAV, CardDAV and WebDAV**. You can also disable the option to have calendars public

```shell
CALDAV_ENABLED=true
CARDDAV_ENABLED=true
WEBDAV_ENABLED=false

PUBLIC_CALENDARS_ENABLED=true
```

> [!NOTE]
>
> By default, `PUBLIC_CALENDARS_ENABLED` is true. That doesn't mean that all calendars are public by default — it just means that you have an option, upon calendar creation, to set the calendar public (but it's not public by default).


**e. Mailer configuration**

It includes:
  - the mailer uri (`MAILER_DSN`)
  - The email address that your invites are going to be sent from

```shell
MAILER_DSN=smtp://user:pass@smtp.example.com:port
INVITE_FROM_ADDRESS=no-reply@example.org
```

> [!WARNING]
> If the username, password or host contain any character considered special in a URI (such as `: / ? # [ ] @ ! $ & ' ( ) * + , ; =`), you MUST encode them.
> See [here](https://symfony.com/doc/current/mailer.html#transport-setup) for more details.

**f. The reminder offset for all birthdays**

You must specify a relative duration, as specified in [the RFC 5545 spec](https://www.rfc-editor.org/rfc/rfc5545.html#section-3.3.6)

```shell
BIRTHDAY_REMINDER_OFFSET=PT9H
```

If you don't want a reminder for birthday events, set it to the `false` value (lowercase):

```shell
BIRTHDAY_REMINDER_OFFSET=false
```

> [!NOTE]
>
> By default, if the env var is not set or empty, we use `PT9H` (9am on the date of the birthday).

**g. The paths for the WebDAV installation**

> [!TIP]
>
> I recommend that you use absolute directories so you know exactly where your files reside.

```shell
WEBDAV_TMP_DIR=/webdav/tmp
WEBDAV_PUBLIC_DIR=/webdav/public
WEBDAV_HOMES_DIR=
```

> [!NOTE]
>
> In a docker setup, I recommend setting `WEBDAV_TMP_DIR` to `/tmp`.

> [!NOTE]
>
> By default, home directories are disabled totally (the env var is set to an empty string). If needed, it is recommended to use a folder that is **NOT** a child of the public dir, such as `/webdav/homes` for instance, so that users cannot access other users' homes.

**h. The log file path**

You can use an absolute file path here, and you can use Symfony's `%kernel.logs_dir%` and `%kernel.environment%` placeholders if needed (as in the default value). Setting it to `/dev/null` will disable logging altogether.

```shell
LOG_FILE_PATH="%kernel.logs_dir%/%kernel.environment%.log"
```

**i. The timezone you want for the app**

This must comply with the [official list](https://www.php.net/manual/en/timezones.php)

```shell
APP_TIMEZONE=Australia/Lord_Howe
```

> Set a void value like so:
> ```shell
> APP_TIMEZONE=
> ```
> in your environment file if you wish to use the **actual default timezone of the server**, and not enforcing it.

**j. Trusting forwarded headers**

If you're behind one or several proxies, the TLS termination might be upstream and the application might not be aware of the HTTPS context. In order for urls to be generated with the correct scheme, you should indicate that you trust the chain of proxies until the TLS termination one. You can use the Symfony mechanism for that (see [documentation](https://symfony.com/doc/7.2/deployment/proxies.html) for possible values):

```shell
SYMFONY_TRUSTED_PROXIES=127.0.0.1,REMOTE_ADDR
```

#### Overriding the dotenv (`.env`) path

You can override the expected location of the environment files (`.env`, `.env.local`, etc) by setting the `ENV_DIR` variable.

The value should be to a _folder_ containing the env files. This value must be specified in the actual environment and *not* in an `.env` file as it is read and evaluated **before** the env files are read.

For instance, you can use it to call `bin/console` with a specific dotenv directory:

```shell
> ENV_DIR=/var/lib/davis bin/console
```

Or use it directly in the Apache configuration

```apache
<VirtualHost *:80>
    # .. rest of config (see ¶ below)

    SetEnv ENV_DIR /var/lib/davis
    # ... other env vars if needed
</VirtualHost>
```

### Specific environment variables for IMAP and LDAP authentication methods

In case you use the `IMAP` auth type, you must specify the auth url (_the "mailbox" url_) in `IMAP_AUTH_URL` as `host:port`, the encryption method (SSL, TLS or None) and whether the certificate should be validated.

You should also explicitely define whether you want new authenticated users to be created upon login:

```shell
IMAP_AUTH_URL=imap.mydomain.com:993
IMAP_ENCRYPTION_METHOD=ssl # ssl, tls or false
IMAP_CERTIFICATE_VALIDATION=true
IMAP_AUTH_USER_AUTOCREATE=true # false by default
```

Same goes for LDAP, where you must specify the LDAP server url, the DN pattern, the Mail attribute, as well as whether you want new authenticated users to be created upon login (_like for IMAP_):

```shell
LDAP_AUTH_URL=ldap://127.0.0.1:3890 # default LDAP port
LDAP_DN_PATTERN=uid=%u,ou=users,dc=domain,dc=com
LDAP_MAIL_ATTRIBUTE=mail
LDAP_AUTH_USER_AUTOCREATE=true # false by default
LDAP_CERTIFICATE_CHECKING_STRATEGY=try # try by default. Other values are: never, hard, demand or allow
```

> Ex: for [Zimbra LDAP](https://zimbra.github.io/adminguide/latest/#zimbra_ldap_service), you might want to use the `zimbraMailDeliveryAddress` attribute to retrieve the principal user email:
>    ```shell
>    LDAP_MAIL_ATTRIBUTE=zimbraMailDeliveryAddress
>    ```

## Migrating from Baïkal?

If you're migrating from Baïkal, then you will likely want to do the following :

1. Get a backup of your data (without the `CREATE`  statements, but with complete `INSERT`  statements):

    ```shell
    mysqldump -u root -p --no-create-info --complete-insert baikal > baikal_to_davis.sql # baikal is the actual name of your database
    ```

2. Create a new database for Davis (let's name it `davis`) and create the base schema:

    ```shell
    bin/console doctrine:migrations:migrate 'DoctrineMigrations\Version20191030113307' --no-interaction
    ```

3. Reimport the data back:

    ```
    mysql -uroot -p davis < baikal_to_davis.sql
    ```

4. Run the necessary remaining migrations:

    ```
    bin/console doctrine:migrations:migrate
    ```

> [!NOTE]
> Some details / steps to resolve are also available in https://github.com/tchapi/davis/issues/226.

# 🌐 Access / Webserver

A simple status page is available on the root `/` of the server.

The administration interface is available at `/dashboard`. You need to login to use it (See `ADMIN_LOGIN` and `ADMIN_PASSWORD` env vars).

The main endpoint for CalDAV, WebDAV or CardDAV is at `/dav`.

> [!TIP]
>
> For shared hosting, the `symfony/apache-pack` is included and provides a standard `.htaccess` file in the public directory so redirections should work out of the box.

## API Endpoint

For user and calendar management there is an API endpoint. See [the API documentation](docs/api/README.md) for more information.

> [!TIP]
>
> The API endpoint requires an environment variable `API_KEY` set to a secret key that you will use in the `X-Davis-API-Token` header of your requests to authenticate. You can generate it with `bin/console api:generate`

## Webserver Configuration Examples

### Example Caddy 2 configuration

```
dav.domain.tld {
    # General settings
    encode zstd gzip
    header {
        -Server
        -X-Powered-By

        # enable HSTS
        Strict-Transport-Security max-age=31536000;

        # disable clients from sniffing the media type
        X-Content-Type-Options nosniff

        # keep referrer data off of HTTP connections
        Referrer-Policy no-referrer-when-downgrade
    }

    root * /var/www/davis/public
    php_fastcgi 127.0.0.1:8000
    file_server
}
```
### Example Apache 2.4 configuration

```apache
<VirtualHost *:80>
    ServerName dav.domain.tld

    DocumentRoot /var/www/davis/public
    DirectoryIndex /index.php

    <Directory /var/www/davis/public>
        AllowOverride None
        Order Allow,Deny
        Allow from All
        FallbackResource /index.php
    </Directory>

    # Apache > 2.4.25, else remove this part
    <Directory /var/www/davis/public/bundles>
        FallbackResource disabled
    </Directory>

    # Env vars (if you did not use .env.local)
    SetEnv APP_ENV prod
    SetEnv APP_SECRET <app-secret-id>
    SetEnv DATABASE_DRIVER "mysql"
    SetEnv DATABASE_URL "mysql://db_user:db_pass@host:3306/db_name?serverVersion=10.9.3-MariaDB&charset=utf8mb4"
    # ... etc
</VirtualHost>
```

### Example Nginx configuration

```nginx
server {
    server_name dav.domain.tld;
    root /var/www/davis/public;

    location / {
        try_files $uri /index.php$is_args$args;
    }

    location /bundles {
        try_files $uri =404;
    }

    location ~ ^/index\.php(/|$) {
        fastcgi_pass unix:/var/run/php/php7.2-fpm.sock; # Change for your PHP version
        fastcgi_split_path_info ^(.+\.php)(/.*)$;
        include fastcgi_params;

        # Env vars (if you did not use .env.local)
        fastcgi_param APP_ENV prod;
        fastcgi_param APP_SECRET <app-secret-id>;
        fastcgi_param DATABASE_DRIVER "mysql";
        fastcgi_param DATABASE_URL "mysql://db_user:db_pass@host:3306/db_name?serverVersion=10.9.3-MariaDB&charset=utf8mb4";
        # ... etc ...

        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        fastcgi_param DOCUMENT_ROOT $realpath_root;
        internal;
    }

    location ~ \.php$ {
        return 404;
    }
}
```

More examples and information [here](https://symfony.com/doc/current/setup/web_server_configuration.html).

## Well-known redirections for CalDAV and CardDAV

Web-based protocols like CalDAV and CardDAV can be found using a discovery service. Some clients require that you implement a path prefix to point to the correct location for your service. See [here](https://en.wikipedia.org/wiki/List_of_/.well-known/_services_offered_by_webservers) for more info.

If you use Apache as your webserver, you can enable the redirections with:

```apache
RewriteEngine On
RewriteRule ^\.well-known/carddav /dav/ [R=301,L]
RewriteRule ^\.well-known/caldav /dav/ [R=301,L]
```

Make sure that `mod_rewrite` is enabled on your installation beforehand.

If you use Nginx, you can add this to your configuration:

```nginx
location / {
    rewrite ^/.well-known/carddav /dav/ redirect;
    rewrite ^/.well-known/caldav /dav/ redirect;
}
```

# 🐳 Dockerized installation

A `Dockerfile` is available for you to compile the image.

To build the checked out version, just run:

    docker build --pull --file docker/Dockerfile --tag davis:latest --build-arg fpm_user=82:82 .

> [!TIP]
>
> The `fpm_user` build arg allows to set:
>  - the uid FPM will run with
>  - the owner of the app folder
>
> This is helpful if you have a proxy that does not use the same default PHP Alpine uid/gid for www-data (82:82). For instance, in the docker compose file, nginx uses 101:101
>

This will build a `davis:latest` image that you can directly use. Do not forget to pass sensible environment variables to the container since the _dist_ `.env` file will take precedence if no `.env.local` or environment variable is found.

You can use `--platform` to specify the platform to build for. Currently, `arm64` (ARMv8) and `amd64` (x86) are supported.

> [!IMPORTANT]
>
> ⚠ Do not forget to run all the database migrations the first time you run the container :
>
>     docker exec -it davis sh -c "APP_ENV=prod bin/console doctrine:migrations:migrate --no-interaction"

## Docker images

For each release, a Docker image is built and published in the [Github package repository](https://github.com/tchapi/davis/pkgs/container/davis).

### Release images

Each release builds and tags two images: one for the standard build (no reverse-proxy) and one for the standalone build (including Caddy as a reverse-proxy). Example:

```
docker pull ghcr.io/tchapi/davis:v4.4.0
```

```
docker pull ghcr.io/tchapi/davis-standalone:v4.4.0
```

### Edge image

The edge image is generally built from the tip of the main branch, but might sometimes be used for specific branch testing:

```
docker pull ghcr.io/tchapi/davis:edge
```

> [!WARNING]
>
> The `edge` image must not be considered stable. **Use only release images for production setups**.

## Full stack

A few `docker-compose.yml` files are also included (in the `docker` folder) as minimal example setups, with various databases for instance.

You can start the containers with :

    cd docker && docker compose up -d

> [!NOTE]
>
> The default recipe above uses MariaDB.

> [!IMPORTANT]
>
> ⚠ Do not forget to run all the database migrations the first time you run the container :
>
>     docker exec -it davis sh -c "APP_ENV=prod bin/console doctrine:migrations:migrate --no-interaction"

> [!WARNING]
>
> For SQLite, you must also make sure that the folder the database will reside in AND the database file in itself have the right permissions! You can do for instance:
> `chown -R www-data: /data` if `/data` is the folder your SQLite database will be in, just after you have run the migrations

### Updating from a previous version

If you update the code, you need to make sure the database structure is in sync.

**Before v3.0.0**, you need to force the update:

    docker exec -it davis sh -c "APP_ENV=prod bin/console doctrine:schema:update --force --no-interaction"

**For v3.0.0 and after**, you can just migrate again (_provided you correctly followed the migration notes in the v3.0.0 release_):

    docker exec -it davis sh -c "APP_ENV=prod bin/console doctrine:migrations:migrate --no-interaction"


Then, head up to `http://<YOUR_DOCKER_IP>:9000` to see the status display :

![Status page](_screenshots/status.png)

> Note that there is no user and no principals created by default.

# NixOS Installation

To install Davis on NixOS, you can use the builtin NixOS module [`services.davis`](https://search.nixos.org/options?channel=unstable&query=services.davis).

Currently the NixOS module and package are in the nixos-unstable channel, but they are slated to enter the stable channel in the 24.05 release.

* [All `services.davis` options](https://search.nixos.org/options?channel=unstable&query=services.davis)
* [Basic Guide](https://nixos.org/manual/nixos/unstable/#module-services-davis)

If you encounter a bug or problem with the NixOS Davis module please open an issue [at the nixpkgs repo](https://github.com/NixOS/nixpkgs/issues/new/choose) so the module maintainers can assist.

# Development

You can spin off a local PHP webserver with:

    php -S localhost:8000 -t public

If you change or add translations, you need to update the `messages` XLIFF file with:

    bin/console translation:extract en --force --domain=messages+intl-icu

## Testing

You can use:

    ./vendor/bin/phpunit

## ✨ Code linting

We use [PHP-CS-Fixer](https://github.com/PHP-CS-Fixer/PHP-CS-Fixer) with:

    PHP_CS_FIXER_IGNORE_ENV=True ./vendor/bin/php-cs-fixer fix

## ❓ How-to's

Below are some issues that can bring more info / insight into custom setups that Davis users have experienced in the past. Hopefully it can help:

  - **Davis on Proxmox / TrueNAS Scale**: https://github.com/tchapi/davis/issues/164


## 🐛 Troubleshooting

Depending on how you run Davis, logs are either:
  - [dev] printed out directly in the console
  - [dev] available in the Symfony Debug Bar in the [Profiler](https://symfony.com/doc/current/profiler.html)
  - [dev] logged in `./var/log/dev.log`
  - [prod] logged in `./var/log/prod.log`, but only if there has been an error (_it's the fingers_crossed filter, explained [here](https://symfony.com/doc/current/logging.html#handlers-that-modify-log-entries)_)

> [!NOTE]
>
> It's `./var/log` (relative to the Davis installation), not `/var/log`.
>
> To tail the aplication log on Docker, do:
> ```
> docker exec -it davis tail /var/www/davis/var/log/prod.log
> ```

### I have a "Bad timezone configuration env var" error on the dashboard

If you see this:

![Bad timezone configuration env var error](_screenshots/bad_timezone_configuration_env_var.png)

It means that the value you set for the `APP_TIMEZONE` env var is not a correct timezone, as per [the official list](https://www.php.net/manual/en/timezones.php). Your timezone has thus not been set and is the server's default (Here, UTC). Adjust the setting accordingly.

### I have a 500 and no tables have been created

You probably forgot to run the migration once to create the necessary DB schema

In Docker:

```shell
docker exec -it davis sh -c "APP_ENV=prod bin/console doctrine:migrations:migrate --no-interaction"
```

In a shell, if you run Davis locally:

    bin/console doctrine:migrations:migrate

### I have a 500 and a log about `Uncaught Error: Class "Symfony\Bundle\WebProfilerBundle\WebProfilerBundle" not found`

You are running the app in dev mode, but you haven't installed the dev dependencies. Either:

a. Set `APP_ENV=prod` in your local env file (See configuration above)

b. Or `composer install` (without the `--no-dev` flag)


### The LDAP connection is not working

> [!NOTE]
>
> Make sure all environment parameters are in plain text (no quotes).

Check if your instance can reach your LDAP server:

  - For Docker instances: make sure it is on the same network
  - Check connection via `ldapsearch`:

    ```shell
    # For docker: connect into container's shell
    docker exec -it davis sh

    # install ldap utils (for alpine linux)
    apk add openldap-clients

    # User checking their own entry
    ldapsearch -H ldap://lldap-server:3890 -D "uid=someuser,ou=users,dc=domain,dc=com" -W -b "dc=domain,dc=com" "(uid=someuser)"
    ```

  - Check that the `LDAP_DN_PATTERN` filter is compliant with your LDAP service
  - Example: `uid=%u,ou=people,dc=domain,dc=com`: [LLDAP](https://github.com/lldap/lldap) uses `people` instead of `users`.

### The birthday calendar is not synced / not up to date

An update event might have been missed. In this case, it's easy to resync all contacts by issuing the command:

```
bin/console dav:sync-birthday-calendar
```

# 📚 Libraries used

  - Symfony 7 (Licence : MIT)
  - Sabre-io/dav (Licence : BSD-3-Clause)
  - Bootstrap 5 (Licence : MIT)

_This project does not use any pipeline for the assets since the frontend side is relatively simple, and based on Bootstrap._

# ⚖️ Licence

This project is release under the MIT licence. See the LICENCE file

[ci_badge]: https://github.com/tchapi/davis/workflows/CI/badge.svg
[ci_link]: https://github.com/tchapi/davis/actions?query=workflow%3ACI

[sponsor_badge]: https://img.shields.io/badge/sponsor%20me-🙏-blue?logo=paypal
[sponsor_link]: https://paypal.me/tchap

[release_badge]: https://img.shields.io/github/v/release/tchapi/davis
[release_link]: https://github.com/tchapi/davis/releases


================================================
FILE: bin/console
================================================
#!/usr/bin/env php
<?php

$overridenEnvDir = getenv('ENV_DIR') ?: null;

if ($overridenEnvDir) {
    // Tell the Runtime not to touch dotenv so we can load our own file.
    // This is needed if the ENV_DIR is outside of the project directory
    $_SERVER['APP_RUNTIME_OPTIONS']['disable_dotenv'] = true;
}

use App\Kernel;
use Symfony\Bundle\FrameworkBundle\Console\Application;
use Symfony\Component\Dotenv\Dotenv;

if (!is_dir(dirname(__DIR__).'/vendor')) {
    throw new LogicException('Dependencies are missing. Try running "composer install".');
}

if (!is_file(dirname(__DIR__).'/vendor/autoload_runtime.php')) {
    throw new LogicException('Symfony Runtime is missing. Try running "composer require symfony/runtime".');
}

require_once dirname(__DIR__).'/vendor/autoload_runtime.php';

if ($overridenEnvDir) {
    // Load our own now, after the runtime has booted
    (new Dotenv())->bootEnv($overridenEnvDir.'/.env');
}

return function (array $context) {
    $kernel = new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);

    return new Application($kernel);
};


================================================
FILE: bin/phpunit
================================================
#!/usr/bin/env php
<?php

if (!ini_get('date.timezone')) {
    ini_set('date.timezone', 'UTC');
}

if (is_file(dirname(__DIR__).'/vendor/phpunit/phpunit/phpunit')) {
    define('PHPUNIT_COMPOSER_INSTALL', dirname(__DIR__).'/vendor/autoload.php');
    require PHPUNIT_COMPOSER_INSTALL;
    PHPUnit\TextUI\Command::main();
} else {
    if (!is_file(dirname(__DIR__).'/vendor/symfony/phpunit-bridge/bin/simple-phpunit.php')) {
        echo "Unable to find the `simple-phpunit.php` script in `vendor/symfony/phpunit-bridge/bin/`.\n";
        exit(1);
    }

    require dirname(__DIR__).'/vendor/symfony/phpunit-bridge/bin/simple-phpunit.php';
}


================================================
FILE: composer.json
================================================
{
    "name": "tchapi/davis",
    "description": "A simple, fully translatable admin interface and frontend for sabre/dav based on Symfony",
    "type": "project",
    "license": "MIT",
    "require": {
        "php": "^8.2",
        "ext-ctype": "*",
        "ext-gd": "*",
        "ext-iconv": "*",
        "ext-zip": "*",
        "composer-runtime-api": "^2",
        "dantsu/php-osm-static-api": "^0.6.4",
        "doctrine/doctrine-bundle": "^2.15.1",
        "doctrine/doctrine-migrations-bundle": "^3.4.2",
        "doctrine/orm": "^2.20.6",
        "sabre/dav": "^4.7.0",
        "symfony/apache-pack": "^1.0.1",
        "symfony/asset": "^7.4",
        "symfony/console": "^7.4",
        "symfony/dotenv": "^7.4",
        "symfony/expression-language": "^7.4",
        "symfony/flex": "^2.7.1",
        "symfony/form": "^7.4",
        "symfony/framework-bundle": "^7.4",
        "symfony/http-client": "^7.4",
        "symfony/intl": "^7.4",
        "symfony/mailer": "^7.4",
        "symfony/monolog-bundle": "^3.10.0",
        "symfony/polyfill-intl-messageformatter": "^1.31",
        "symfony/process": "^7.4",
        "symfony/property-access": "^7.4",
        "symfony/property-info": "^7.4",
        "symfony/runtime": "^7.4",
        "symfony/security-bundle": "^7.4",
        "symfony/serializer": "^7.4",
        "symfony/translation": "^7.4",
        "symfony/twig-bundle": "^7.4",
        "symfony/validator": "^7.4",
        "symfony/web-link": "^7.4",
        "symfony/yaml": "^7.4",
        "webklex/php-imap": "^6.2"
    },
    "require-dev": {
        "doctrine/doctrine-fixtures-bundle": "^3.5",
        "friendsofphp/php-cs-fixer": "^3.49.0",
        "phpunit/phpunit": "^10.5.10",
        "symfony/browser-kit": "^7.4",
        "symfony/css-selector": "^7.4",
        "symfony/debug-bundle": "^7.4",
        "symfony/maker-bundle": "^1.54",
        "symfony/phpunit-bridge": "^7.4",
        "symfony/stopwatch": "^7.4",
        "symfony/web-profiler-bundle": "^7.4"
    },
    "config": {
        "preferred-install": {
            "*": "dist"
        },
        "sort-packages": true,
        "platform": {
            "php": "8.2.15"
        },
        "allow-plugins": {
            "composer/package-versions-deprecated": true,
            "symfony/flex": true,
            "symfony/runtime": true
        }
    },
    "autoload": {
        "psr-4": {
            "App\\": "src/"
        }
    },
    "autoload-dev": {
        "psr-4": {
            "App\\Tests\\": "tests/"
        }
    },
    "scripts": {
        "auto-scripts": {
            "cache:clear": "symfony-cmd",
            "assets:install %PUBLIC_DIR%": "symfony-cmd"
        },
        "post-install-cmd": [
            "@auto-scripts"
        ],
        "post-update-cmd": [
            "@auto-scripts"
        ]
    },
    "conflict": {
        "symfony/symfony": "*"
    },
    "extra": {
        "symfony": {
            "allow-contrib": false,
            "require": "7.4.*"
        }
    }
}


================================================
FILE: config/bundles.php
================================================
<?php

return [
    Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true],
    Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true],
    Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true],
    Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true],
    Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true],
    Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true],
    Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true],
    Symfony\Bundle\DebugBundle\DebugBundle::class => ['dev' => true, 'test' => true],
    Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true],
    Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle::class => ['dev' => true, 'test' => true],
];


================================================
FILE: config/packages/cache.yaml
================================================
framework:
    cache:
        # Unique name of your app: used to compute stable namespaces for cache keys.
        #prefix_seed: your_vendor_name/app_name

        # The "app" cache stores to the filesystem by default.
        # The data in this cache should persist between deploys.
        # Other options include:

        # Redis
        #app: cache.adapter.redis
        #default_redis_provider: redis://localhost

        # APCu (not recommended with heavy random-write workloads as memory fragmentation can cause perf issues)
        #app: cache.adapter.apcu

        # Namespaced pools use the above "app" backend by default
        #pools:
            #my.dedicated.cache: null


================================================
FILE: config/packages/dev/debug.yaml
================================================
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)%"


================================================
FILE: config/packages/dev/monolog.yaml
================================================
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"]


================================================
FILE: config/packages/dev/web_profiler.yaml
================================================
web_profiler:
    toolbar: true
    intercept_redirects: false

framework:
    profiler: { only_exceptions: false }


================================================
FILE: config/packages/doctrine.yaml
================================================
doctrine:
    dbal:
        # The server_version must be configured directly in the
        # DATABASE_URL to allow different drivers without adding
        # too many env vars
        driver: 'pdo_%env(string:default:default_database_driver:DATABASE_DRIVER)%'

        url: '%env(resolve:DATABASE_URL)%'

    orm:
        auto_generate_proxy_classes: true
        report_fields_where_declared: true
        enable_lazy_ghost_objects: true
        naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware
        controller_resolver:
            auto_mapping: false
        mappings:
            App:
                is_bundle: false
                type: attribute
                dir: '%kernel.project_dir%/src/Entity'
                prefix: 'App\Entity'
                alias: App


================================================
FILE: config/packages/doctrine_migrations.yaml
================================================
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'


================================================
FILE: config/packages/framework.yaml
================================================
# see https://symfony.com/doc/current/reference/configuration/framework.html
framework:
    secret: '%env(APP_SECRET)%'
    handle_all_throwables: true
    #csrf_protection: true
    http_method_override: false

    # Enables session support. Note that the session will ONLY be started if you read or write from it.
    # Remove or comment this section to explicitly disable session support.
    session:
        handler_id: null
        cookie_secure: auto
        cookie_samesite: lax
        name: 'DAVIS_SESSION'
        storage_factory_id: session.storage.factory.native

    profiler:
        collect_serializer_data: true

    property_info:
        with_constructor_extractor: false

    php_errors:
        log: true


================================================
FILE: config/packages/mailer.yaml
================================================
framework:
    mailer:
        dsn: '%env(MAILER_DSN)%'


================================================
FILE: config/packages/prod/deprecations.yaml
================================================
# As of Symfony 5.1, deprecations are logged in the dedicated "deprecation" channel when it exists
#monolog:
#    channels: [deprecation]
#    handlers:
#        deprecation:
#            type: stream
#            channels: [deprecation]
#            path: "%kernel.logs_dir%/%kernel.environment%.deprecations.log"


================================================
FILE: config/packages/prod/doctrine.yaml
================================================
doctrine:
    orm:
        auto_generate_proxy_classes: false
        metadata_cache_driver:
            type: pool
            pool: doctrine.system_cache_pool
        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


================================================
FILE: config/packages/prod/monolog.yaml
================================================
monolog:
    handlers:
        main:
            type: fingers_crossed
            action_level: error
            handler: nested
            excluded_http_codes: [404, 405]
            buffer_size: 50 # How many messages should be saved? Prevent memory leaks
        nested:
            type: stream
            path: "%env(resolve:LOG_FILE_PATH)%"
            level: debug
        console:
            type: console
            process_psr_3_messages: false
            channels: ["!event", "!doctrine"]


================================================
FILE: config/packages/prod/routing.yaml
================================================
framework:
    router:
        strict_requirements: null


================================================
FILE: config/packages/routing.yaml
================================================
framework:
    router:
        utf8: true

        # 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


================================================
FILE: config/packages/security.yaml
================================================
security:
    password_hashers:
        Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'
    providers:
        admin_user_provider:
            id: App\Security\AdminUserProvider
    firewalls:
        dev:
            pattern: ^/(_(profiler|wdt)|css|images|js)/
            security: false
        api_v1:
            pattern: ^/api/v1
            stateless: true
            custom_authenticators:
                - App\Security\ApiKeyAuthenticator
        main:
            lazy: true
            custom_authenticators:
                - App\Security\LoginFormAuthenticator
            provider: admin_user_provider
            logout:
                path: app_logout
                target: dashboard
            

    access_control:
        - { path: ^/$, roles: PUBLIC_ACCESS }
        - { path: ^/dav, roles: PUBLIC_ACCESS }
        - { path: ^/dashboard, roles: ROLE_ADMIN, allow_if: "'%env(default:default_admin_auth_bypass:ADMIN_AUTH_BYPASS)%' === 'true'" }
        - { path: ^/users, roles: ROLE_ADMIN, allow_if: "'%env(default:default_admin_auth_bypass:ADMIN_AUTH_BYPASS)%' === 'true'" }
        - { path: ^/calendars, roles: ROLE_ADMIN, allow_if: "'%env(default:default_admin_auth_bypass:ADMIN_AUTH_BYPASS)%' === 'true'" }
        - { path: ^/adressbooks, roles: ROLE_ADMIN, allow_if: "'%env(default:default_admin_auth_bypass:ADMIN_AUTH_BYPASS)%' === 'true'" }
        - { path: ^/api/v1/health$, roles: PUBLIC_ACCESS }
        - { path: ^/api, roles: IS_AUTHENTICATED }


================================================
FILE: config/packages/test/framework.yaml
================================================
framework:
    test: true
    session:
        storage_factory_id: session.storage.factory.mock_file

================================================
FILE: config/packages/test/monolog.yaml
================================================
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


================================================
FILE: config/packages/test/twig.yaml
================================================
twig:
    strict_variables: true


================================================
FILE: config/packages/test/validator.yaml
================================================
framework:
    validation:
        not_compromised_password: false


================================================
FILE: config/packages/test/web_profiler.yaml
================================================
web_profiler:
    toolbar: false
    intercept_redirects: false

framework:
    profiler: { collect: false }


================================================
FILE: config/packages/translation.yaml
================================================
framework:
    default_locale: en
    translator:
        default_path: '%kernel.project_dir%/translations'
        fallbacks:
            - en


================================================
FILE: config/packages/twig.yaml
================================================
twig:
    default_path: '%kernel.project_dir%/templates'
    debug: '%kernel.debug%'
    strict_variables: '%kernel.debug%'
    form_themes: ['bootstrap_5_horizontal_layout.html.twig']
    globals:
        invite_from_address: '%env(INVITE_FROM_ADDRESS)%'
        calDAVEnabled: '%env(bool:CALDAV_ENABLED)%'
        cardDAVEnabled: '%env(bool:CARDDAV_ENABLED)%'
        webDAVEnabled: '%env(bool:WEBDAV_ENABLED)%'
        authRealm: '%env(AUTH_REALM)%'
        authMethod: '%env(AUTH_METHOD)%'


================================================
FILE: config/packages/validator.yaml
================================================
framework:
    validation:
        enable_attributes: true
        email_validation_mode: html5

        # Enables validator auto-mapping support.
        # For instance, basic validation constraints will be inferred from Doctrine's metadata.
        #auto_mapping:
        #    App\Entity\: []


================================================
FILE: config/reference.php
================================================
<?php

// This file is auto-generated and is for apps only. Bundles SHOULD NOT rely on its content.

namespace Symfony\Component\DependencyInjection\Loader\Configurator;

use Symfony\Component\Config\Loader\ParamConfigurator as Param;

/**
 * This class provides array-shapes for configuring the services and bundles of an application.
 *
 * Services declared with the config() method below are autowired and autoconfigured by default.
 *
 * This is for apps only. Bundles SHOULD NOT use it.
 *
 * Example:
 *
 *     ```php
 *     // config/services.php
 *     namespace Symfony\Component\DependencyInjection\Loader\Configurator;
 *
 *     return App::config([
 *         'services' => [
 *             'App\\' => [
 *                 'resource' => '../src/',
 *             ],
 *         ],
 *     ]);
 *     ```
 *
 * @psalm-type ImportsConfig = list<string|array{
 *     resource: string,
 *     type?: string|null,
 *     ignore_errors?: bool,
 * }>
 * @psalm-type ParametersConfig = array<string, scalar|\UnitEnum|array<scalar|\UnitEnum|array<mixed>|Param|null>|Param|null>
 * @psalm-type ArgumentsType = list<mixed>|array<string, mixed>
 * @psalm-type CallType = array<string, ArgumentsType>|array{0:string, 1?:ArgumentsType, 2?:bool}|array{method:string, arguments?:ArgumentsType, returns_clone?:bool}
 * @psalm-type TagsType = list<string|array<string, array<string, mixed>>> // arrays inside the list must have only one element, with the tag name as the key
 * @psalm-type CallbackType = string|array{0:string|ReferenceConfigurator,1:string}|\Closure|ReferenceConfigurator|ExpressionConfigurator
 * @psalm-type DeprecationType = array{package: string, version: string, message?: string}
 * @psalm-type DefaultsType = array{
 *     public?: bool,
 *     tags?: TagsType,
 *     resource_tags?: TagsType,
 *     autowire?: bool,
 *     autoconfigure?: bool,
 *     bind?: array<string, mixed>,
 * }
 * @psalm-type InstanceofType = array{
 *     shared?: bool,
 *     lazy?: bool|string,
 *     public?: bool,
 *     properties?: array<string, mixed>,
 *     configurator?: CallbackType,
 *     calls?: list<CallType>,
 *     tags?: TagsType,
 *     resource_tags?: TagsType,
 *     autowire?: bool,
 *     bind?: array<string, mixed>,
 *     constructor?: string,
 * }
 * @psalm-type DefinitionType = array{
 *     class?: string,
 *     file?: string,
 *     parent?: string,
 *     shared?: bool,
 *     synthetic?: bool,
 *     lazy?: bool|string,
 *     public?: bool,
 *     abstract?: bool,
 *     deprecated?: DeprecationType,
 *     factory?: CallbackType,
 *     configurator?: CallbackType,
 *     arguments?: ArgumentsType,
 *     properties?: array<string, mixed>,
 *     calls?: list<CallType>,
 *     tags?: TagsType,
 *     resource_tags?: TagsType,
 *     decorates?: string,
 *     decoration_inner_name?: string,
 *     decoration_priority?: int,
 *     decoration_on_invalid?: 'exception'|'ignore'|null,
 *     autowire?: bool,
 *     autoconfigure?: bool,
 *     bind?: array<string, mixed>,
 *     constructor?: string,
 *     from_callable?: CallbackType,
 * }
 * @psalm-type AliasType = string|array{
 *     alias: string,
 *     public?: bool,
 *     deprecated?: DeprecationType,
 * }
 * @psalm-type PrototypeType = array{
 *     resource: string,
 *     namespace?: string,
 *     exclude?: string|list<string>,
 *     parent?: string,
 *     shared?: bool,
 *     lazy?: bool|string,
 *     public?: bool,
 *     abstract?: bool,
 *     deprecated?: DeprecationType,
 *     factory?: CallbackType,
 *     arguments?: ArgumentsType,
 *     properties?: array<string, mixed>,
 *     configurator?: CallbackType,
 *     calls?: list<CallType>,
 *     tags?: TagsType,
 *     resource_tags?: TagsType,
 *     autowire?: bool,
 *     autoconfigure?: bool,
 *     bind?: array<string, mixed>,
 *     constructor?: string,
 * }
 * @psalm-type StackType = array{
 *     stack: list<DefinitionType|AliasType|PrototypeType|array<class-string, ArgumentsType|null>>,
 *     public?: bool,
 *     deprecated?: DeprecationType,
 * }
 * @psalm-type ServicesConfig = array{
 *     _defaults?: DefaultsType,
 *     _instanceof?: InstanceofType,
 *     ...<string, DefinitionType|AliasType|PrototypeType|StackType|ArgumentsType|null>
 * }
 * @psalm-type ExtensionType = array<string, mixed>
 * @psalm-type FrameworkConfig = array{
 *     secret?: scalar|Param|null,
 *     http_method_override?: bool|Param, // Set true to enable support for the '_method' request parameter to determine the intended HTTP method on POST requests. // Default: false
 *     allowed_http_method_override?: list<string|Param>|null,
 *     trust_x_sendfile_type_header?: scalar|Param|null, // Set true to enable support for xsendfile in binary file responses. // Default: "%env(bool:default::SYMFONY_TRUST_X_SENDFILE_TYPE_HEADER)%"
 *     ide?: scalar|Param|null, // Default: "%env(default::SYMFONY_IDE)%"
 *     test?: bool|Param,
 *     default_locale?: scalar|Param|null, // Default: "en"
 *     set_locale_from_accept_language?: bool|Param, // Whether to use the Accept-Language HTTP header to set the Request locale (only when the "_locale" request attribute is not passed). // Default: false
 *     set_content_language_from_locale?: bool|Param, // Whether to set the Content-Language HTTP header on the Response using the Request locale. // Default: false
 *     enabled_locales?: list<scalar|Param|null>,
 *     trusted_hosts?: list<scalar|Param|null>,
 *     trusted_proxies?: mixed, // Default: ["%env(default::SYMFONY_TRUSTED_PROXIES)%"]
 *     trusted_headers?: list<scalar|Param|null>,
 *     error_controller?: scalar|Param|null, // Default: "error_controller"
 *     handle_all_throwables?: bool|Param, // HttpKernel will handle all kinds of \Throwable. // Default: true
 *     csrf_protection?: bool|array{
 *         enabled?: scalar|Param|null, // Default: null
 *         stateless_token_ids?: list<scalar|Param|null>,
 *         check_header?: scalar|Param|null, // Whether to check the CSRF token in a header in addition to a cookie when using stateless protection. // Default: false
 *         cookie_name?: scalar|Param|null, // The name of the cookie to use when using stateless protection. // Default: "csrf-token"
 *     },
 *     form?: bool|array{ // Form configuration
 *         enabled?: bool|Param, // Default: true
 *         csrf_protection?: bool|array{
 *             enabled?: scalar|Param|null, // Default: null
 *             token_id?: scalar|Param|null, // Default: null
 *             field_name?: scalar|Param|null, // Default: "_token"
 *             field_attr?: array<string, scalar|Param|null>,
 *         },
 *     },
 *     http_cache?: bool|array{ // HTTP cache configuration
 *         enabled?: bool|Param, // Default: false
 *         debug?: bool|Param, // Default: "%kernel.debug%"
 *         trace_level?: "none"|"short"|"full"|Param,
 *         trace_header?: scalar|Param|null,
 *         default_ttl?: int|Param,
 *         private_headers?: list<scalar|Param|null>,
 *         skip_response_headers?: list<scalar|Param|null>,
 *         allow_reload?: bool|Param,
 *         allow_revalidate?: bool|Param,
 *         stale_while_revalidate?: int|Param,
 *         stale_if_error?: int|Param,
 *         terminate_on_cache_hit?: bool|Param,
 *     },
 *     esi?: bool|array{ // ESI configuration
 *         enabled?: bool|Param, // Default: false
 *     },
 *     ssi?: bool|array{ // SSI configuration
 *         enabled?: bool|Param, // Default: false
 *     },
 *     fragments?: bool|array{ // Fragments configuration
 *         enabled?: bool|Param, // Default: false
 *         hinclude_default_template?: scalar|Param|null, // Default: null
 *         path?: scalar|Param|null, // Default: "/_fragment"
 *     },
 *     profiler?: bool|array{ // Profiler configuration
 *         enabled?: bool|Param, // Default: false
 *         collect?: bool|Param, // Default: true
 *         collect_parameter?: scalar|Param|null, // The name of the parameter to use to enable or disable collection on a per request basis. // Default: null
 *         only_exceptions?: bool|Param, // Default: false
 *         only_main_requests?: bool|Param, // Default: false
 *         dsn?: scalar|Param|null, // Default: "file:%kernel.cache_dir%/profiler"
 *         collect_serializer_data?: bool|Param, // Enables the serializer data collector and profiler panel. // Default: false
 *     },
 *     workflows?: bool|array{
 *         enabled?: bool|Param, // Default: false
 *         workflows?: array<string, array{ // Default: []
 *             audit_trail?: bool|array{
 *                 enabled?: bool|Param, // Default: false
 *             },
 *             type?: "workflow"|"state_machine"|Param, // Default: "state_machine"
 *             marking_store?: array{
 *                 type?: "method"|Param,
 *                 property?: scalar|Param|null,
 *                 service?: scalar|Param|null,
 *             },
 *             supports?: list<scalar|Param|null>,
 *             definition_validators?: list<scalar|Param|null>,
 *             support_strategy?: scalar|Param|null,
 *             initial_marking?: list<scalar|Param|null>,
 *             events_to_dispatch?: list<string|Param>|null,
 *             places?: list<array{ // Default: []
 *                 name?: scalar|Param|null,
 *                 metadata?: array<string, mixed>,
 *             }>,
 *             transitions?: list<array{ // Default: []
 *                 name?: string|Param,
 *                 guard?: string|Param, // An expression to block the transition.
 *                 from?: list<array{ // Default: []
 *                     place?: string|Param,
 *                     weight?: int|Param, // Default: 1
 *                 }>,
 *                 to?: list<array{ // Default: []
 *                     place?: string|Param,
 *                     weight?: int|Param, // Default: 1
 *                 }>,
 *                 weight?: int|Param, // Default: 1
 *                 metadata?: array<string, mixed>,
 *             }>,
 *             metadata?: array<string, mixed>,
 *         }>,
 *     },
 *     router?: bool|array{ // Router configuration
 *         enabled?: bool|Param, // Default: false
 *         resource?: scalar|Param|null,
 *         type?: scalar|Param|null,
 *         cache_dir?: scalar|Param|null, // Deprecated: Setting the "framework.router.cache_dir.cache_dir" configuration option is deprecated. It will be removed in version 8.0. // Default: "%kernel.build_dir%"
 *         default_uri?: scalar|Param|null, // The default URI used to generate URLs in a non-HTTP context. // Default: null
 *         http_port?: scalar|Param|null, // Default: 80
 *         https_port?: scalar|Param|null, // Default: 443
 *         strict_requirements?: scalar|Param|null, // set to true to throw an exception when a parameter does not match the requirements set to false to disable exceptions when a parameter does not match the requirements (and return null instead) set to null to disable parameter checks against requirements 'true' is the preferred configuration in development mode, while 'false' or 'null' might be preferred in production // Default: true
 *         utf8?: bool|Param, // Default: true
 *     },
 *     session?: bool|array{ // Session configuration
 *         enabled?: bool|Param, // Default: false
 *         storage_factory_id?: scalar|Param|null, // Default: "session.storage.factory.native"
 *         handler_id?: scalar|Param|null, // Defaults to using the native session handler, or to the native *file* session handler if "save_path" is not null.
 *         name?: scalar|Param|null,
 *         cookie_lifetime?: scalar|Param|null,
 *         cookie_path?: scalar|Param|null,
 *         cookie_domain?: scalar|Param|null,
 *         cookie_secure?: true|false|"auto"|Param, // Default: "auto"
 *         cookie_httponly?: bool|Param, // Default: true
 *         cookie_samesite?: null|"lax"|"strict"|"none"|Param, // Default: "lax"
 *         use_cookies?: bool|Param,
 *         gc_divisor?: scalar|Param|null,
 *         gc_probability?: scalar|Param|null,
 *         gc_maxlifetime?: scalar|Param|null,
 *         save_path?: scalar|Param|null, // Defaults to "%kernel.cache_dir%/sessions" if the "handler_id" option is not null.
 *         metadata_update_threshold?: int|Param, // Seconds to wait between 2 session metadata updates. // Default: 0
 *         sid_length?: int|Param, // Deprecated: Setting the "framework.session.sid_length.sid_length" configuration option is deprecated. It will be removed in version 8.0. No alternative is provided as PHP 8.4 has deprecated the related option.
 *         sid_bits_per_character?: int|Param, // Deprecated: Setting the "framework.session.sid_bits_per_character.sid_bits_per_character" configuration option is deprecated. It will be removed in version 8.0. No alternative is provided as PHP 8.4 has deprecated the related option.
 *     },
 *     request?: bool|array{ // Request configuration
 *         enabled?: bool|Param, // Default: false
 *         formats?: array<string, string|list<scalar|Param|null>>,
 *     },
 *     assets?: bool|array{ // Assets configuration
 *         enabled?: bool|Param, // Default: true
 *         strict_mode?: bool|Param, // Throw an exception if an entry is missing from the manifest.json. // Default: false
 *         version_strategy?: scalar|Param|null, // Default: null
 *         version?: scalar|Param|null, // Default: null
 *         version_format?: scalar|Param|null, // Default: "%%s?%%s"
 *         json_manifest_path?: scalar|Param|null, // Default: null
 *         base_path?: scalar|Param|null, // Default: ""
 *         base_urls?: list<scalar|Param|null>,
 *         packages?: array<string, array{ // Default: []
 *             strict_mode?: bool|Param, // Throw an exception if an entry is missing from the manifest.json. // Default: false
 *             version_strategy?: scalar|Param|null, // Default: null
 *             version?: scalar|Param|null,
 *             version_format?: scalar|Param|null, // Default: null
 *             json_manifest_path?: scalar|Param|null, // Default: null
 *             base_path?: scalar|Param|null, // Default: ""
 *             base_urls?: list<scalar|Param|null>,
 *         }>,
 *     },
 *     asset_mapper?: bool|array{ // Asset Mapper configuration
 *         enabled?: bool|Param, // Default: false
 *         paths?: array<string, scalar|Param|null>,
 *         excluded_patterns?: list<scalar|Param|null>,
 *         exclude_dotfiles?: bool|Param, // If true, any files starting with "." will be excluded from the asset mapper. // Default: true
 *         server?: bool|Param, // If true, a "dev server" will return the assets from the public directory (true in "debug" mode only by default). // Default: true
 *         public_prefix?: scalar|Param|null, // The public path where the assets will be written to (and served from when "server" is true). // Default: "/assets/"
 *         missing_import_mode?: "strict"|"warn"|"ignore"|Param, // Behavior if an asset cannot be found when imported from JavaScript or CSS files - e.g. "import './non-existent.js'". "strict" means an exception is thrown, "warn" means a warning is logged, "ignore" means the import is left as-is. // Default: "warn"
 *         extensions?: array<string, scalar|Param|null>,
 *         importmap_path?: scalar|Param|null, // The path of the importmap.php file. // Default: "%kernel.project_dir%/importmap.php"
 *         importmap_polyfill?: scalar|Param|null, // The importmap name that will be used to load the polyfill. Set to false to disable. // Default: "es-module-shims"
 *         importmap_script_attributes?: array<string, scalar|Param|null>,
 *         vendor_dir?: scalar|Param|null, // The directory to store JavaScript vendors. // Default: "%kernel.project_dir%/assets/vendor"
 *         precompress?: bool|array{ // Precompress assets with Brotli, Zstandard and gzip.
 *             enabled?: bool|Param, // Default: false
 *             formats?: list<scalar|Param|null>,
 *             extensions?: list<scalar|Param|null>,
 *         },
 *     },
 *     translator?: bool|array{ // Translator configuration
 *         enabled?: bool|Param, // Default: true
 *         fallbacks?: list<scalar|Param|null>,
 *         logging?: bool|Param, // Default: false
 *         formatter?: scalar|Param|null, // Default: "translator.formatter.default"
 *         cache_dir?: scalar|Param|null, // Default: "%kernel.cache_dir%/translations"
 *         default_path?: scalar|Param|null, // The default path used to load translations. // Default: "%kernel.project_dir%/translations"
 *         paths?: list<scalar|Param|null>,
 *         pseudo_localization?: bool|array{
 *             enabled?: bool|Param, // Default: false
 *             accents?: bool|Param, // Default: true
 *             expansion_factor?: float|Param, // Default: 1.0
 *             brackets?: bool|Param, // Default: true
 *             parse_html?: bool|Param, // Default: false
 *             localizable_html_attributes?: list<scalar|Param|null>,
 *         },
 *         providers?: array<string, array{ // Default: []
 *             dsn?: scalar|Param|null,
 *             domains?: list<scalar|Param|null>,
 *             locales?: list<scalar|Param|null>,
 *         }>,
 *         globals?: array<string, string|array{ // Default: []
 *             value?: mixed,
 *             message?: string|Param,
 *             parameters?: array<string, scalar|Param|null>,
 *             domain?: string|Param,
 *         }>,
 *     },
 *     validation?: bool|array{ // Validation configuration
 *         enabled?: bool|Param, // Default: true
 *         cache?: scalar|Param|null, // Deprecated: Setting the "framework.validation.cache.cache" configuration option is deprecated. It will be removed in version 8.0.
 *         enable_attributes?: bool|Param, // Default: true
 *         static_method?: list<scalar|Param|null>,
 *         translation_domain?: scalar|Param|null, // Default: "validators"
 *         email_validation_mode?: "html5"|"html5-allow-no-tld"|"strict"|"loose"|Param, // Default: "html5"
 *         mapping?: array{
 *             paths?: list<scalar|Param|null>,
 *         },
 *         not_compromised_password?: bool|array{
 *             enabled?: bool|Param, // When disabled, compromised passwords will be accepted as valid. // Default: true
 *             endpoint?: scalar|Param|null, // API endpoint for the NotCompromisedPassword Validator. // Default: null
 *         },
 *         disable_translation?: bool|Param, // Default: false
 *         auto_mapping?: array<string, array{ // Default: []
 *             services?: list<scalar|Param|null>,
 *         }>,
 *     },
 *     annotations?: bool|array{
 *         enabled?: bool|Param, // Default: false
 *     },
 *     serializer?: bool|array{ // Serializer configuration
 *         enabled?: bool|Param, // Default: true
 *         enable_attributes?: bool|Param, // Default: true
 *         name_converter?: scalar|Param|null,
 *         circular_reference_handler?: scalar|Param|null,
 *         max_depth_handler?: scalar|Param|null,
 *         mapping?: array{
 *             paths?: list<scalar|Param|null>,
 *         },
 *         default_context?: array<string, mixed>,
 *         named_serializers?: array<string, array{ // Default: []
 *             name_converter?: scalar|Param|null,
 *             default_context?: array<string, mixed>,
 *             include_built_in_normalizers?: bool|Param, // Whether to include the built-in normalizers // Default: true
 *             include_built_in_encoders?: bool|Param, // Whether to include the built-in encoders // Default: true
 *         }>,
 *     },
 *     property_access?: bool|array{ // Property access configuration
 *         enabled?: bool|Param, // Default: true
 *         magic_call?: bool|Param, // Default: false
 *         magic_get?: bool|Param, // Default: true
 *         magic_set?: bool|Param, // Default: true
 *         throw_exception_on_invalid_index?: bool|Param, // Default: false
 *         throw_exception_on_invalid_property_path?: bool|Param, // Default: true
 *     },
 *     type_info?: bool|array{ // Type info configuration
 *         enabled?: bool|Param, // Default: true
 *         aliases?: array<string, scalar|Param|null>,
 *     },
 *     property_info?: bool|array{ // Property info configuration
 *         enabled?: bool|Param, // Default: true
 *         with_constructor_extractor?: bool|Param, // Registers the constructor extractor.
 *     },
 *     cache?: array{ // Cache configuration
 *         prefix_seed?: scalar|Param|null, // Used to namespace cache keys when using several apps with the same shared backend. // Default: "_%kernel.project_dir%.%kernel.container_class%"
 *         app?: scalar|Param|null, // App related cache pools configuration. // Default: "cache.adapter.filesystem"
 *         system?: scalar|Param|null, // System related cache pools configuration. // Default: "cache.adapter.system"
 *         directory?: scalar|Param|null, // Default: "%kernel.share_dir%/pools/app"
 *         default_psr6_provider?: scalar|Param|null,
 *         default_redis_provider?: scalar|Param|null, // Default: "redis://localhost"
 *         default_valkey_provider?: scalar|Param|null, // Default: "valkey://localhost"
 *         default_memcached_provider?: scalar|Param|null, // Default: "memcached://localhost"
 *         default_doctrine_dbal_provider?: scalar|Param|null, // Default: "database_connection"
 *         default_pdo_provider?: scalar|Param|null, // Default: null
 *         pools?: array<string, array{ // Default: []
 *             adapters?: list<scalar|Param|null>,
 *             tags?: scalar|Param|null, // Default: null
 *             public?: bool|Param, // Default: false
 *             default_lifetime?: scalar|Param|null, // Default lifetime of the pool.
 *             provider?: scalar|Param|null, // Overwrite the setting from the default provider for this adapter.
 *             early_expiration_message_bus?: scalar|Param|null,
 *             clearer?: scalar|Param|null,
 *         }>,
 *     },
 *     php_errors?: array{ // PHP errors handling configuration
 *         log?: mixed, // Use the application logger instead of the PHP logger for logging PHP errors. // Default: true
 *         throw?: bool|Param, // Throw PHP errors as \ErrorException instances. // Default: true
 *     },
 *     exceptions?: array<string, array{ // Default: []
 *         log_level?: scalar|Param|null, // The level of log message. Null to let Symfony decide. // Default: null
 *         status_code?: scalar|Param|null, // The status code of the response. Null or 0 to let Symfony decide. // Default: null
 *         log_channel?: scalar|Param|null, // The channel of log message. Null to let Symfony decide. // Default: null
 *     }>,
 *     web_link?: bool|array{ // Web links configuration
 *         enabled?: bool|Param, // Default: true
 *     },
 *     lock?: bool|string|array{ // Lock configuration
 *         enabled?: bool|Param, // Default: false
 *         resources?: array<string, string|list<scalar|Param|null>>,
 *     },
 *     semaphore?: bool|string|array{ // Semaphore configuration
 *         enabled?: bool|Param, // Default: false
 *         resources?: array<string, scalar|Param|null>,
 *     },
 *     messenger?: bool|array{ // Messenger configuration
 *         enabled?: bool|Param, // Default: false
 *         routing?: array<string, string|array{ // Default: []
 *             senders?: list<scalar|Param|null>,
 *         }>,
 *         serializer?: array{
 *             default_serializer?: scalar|Param|null, // Service id to use as the default serializer for the transports. // Default: "messenger.transport.native_php_serializer"
 *             symfony_serializer?: array{
 *                 format?: scalar|Param|null, // Serialization format for the messenger.transport.symfony_serializer service (which is not the serializer used by default). // Default: "json"
 *                 context?: array<string, mixed>,
 *             },
 *         },
 *         transports?: array<string, string|array{ // Default: []
 *             dsn?: scalar|Param|null,
 *             serializer?: scalar|Param|null, // Service id of a custom serializer to use. // Default: null
 *             options?: array<string, mixed>,
 *             failure_transport?: scalar|Param|null, // Transport name to send failed messages to (after all retries have failed). // Default: null
 *             retry_strategy?: string|array{
 *                 service?: scalar|Param|null, // Service id to override the retry strategy entirely. // Default: null
 *                 max_retries?: int|Param, // Default: 3
 *                 delay?: int|Param, // Time in ms to delay (or the initial value when multiplier is used). // Default: 1000
 *                 multiplier?: float|Param, // If greater than 1, delay will grow exponentially for each retry: this delay = (delay * (multiple ^ retries)). // Default: 2
 *                 max_delay?: int|Param, // Max time in ms that a retry should ever be delayed (0 = infinite). // Default: 0
 *                 jitter?: float|Param, // Randomness to apply to the delay (between 0 and 1). // Default: 0.1
 *             },
 *             rate_limiter?: scalar|Param|null, // Rate limiter name to use when processing messages. // Default: null
 *         }>,
 *         failure_transport?: scalar|Param|null, // Transport name to send failed messages to (after all retries have failed). // Default: null
 *         stop_worker_on_signals?: list<scalar|Param|null>,
 *         default_bus?: scalar|Param|null, // Default: null
 *         buses?: array<string, array{ // Default: {"messenger.bus.default":{"default_middleware":{"enabled":true,"allow_no_handlers":false,"allow_no_senders":true},"middleware":[]}}
 *             default_middleware?: bool|string|array{
 *                 enabled?: bool|Param, // Default: true
 *                 allow_no_handlers?: bool|Param, // Default: false
 *                 allow_no_senders?: bool|Param, // Default: true
 *             },
 *             middleware?: list<string|array{ // Default: []
 *                 id?: scalar|Param|null,
 *                 arguments?: list<mixed>,
 *             }>,
 *         }>,
 *     },
 *     scheduler?: bool|array{ // Scheduler configuration
 *         enabled?: bool|Param, // Default: false
 *     },
 *     disallow_search_engine_index?: bool|Param, // Enabled by default when debug is enabled. // Default: true
 *     http_client?: bool|array{ // HTTP Client configuration
 *         enabled?: bool|Param, // Default: true
 *         max_host_connections?: int|Param, // The maximum number of connections to a single host.
 *         default_options?: array{
 *             headers?: array<string, mixed>,
 *             vars?: array<string, mixed>,
 *             max_redirects?: int|Param, // The maximum number of redirects to follow.
 *             http_version?: scalar|Param|null, // The default HTTP version, typically 1.1 or 2.0, leave to null for the best version.
 *             resolve?: array<string, scalar|Param|null>,
 *             proxy?: scalar|Param|null, // The URL of the proxy to pass requests through or null for automatic detection.
 *             no_proxy?: scalar|Param|null, // A comma separated list of hosts that do not require a proxy to be reached.
 *             timeout?: float|Param, // The idle timeout, defaults to the "default_socket_timeout" ini parameter.
 *             max_duration?: float|Param, // The maximum execution time for the request+response as a whole.
 *             bindto?: scalar|Param|null, // A network interface name, IP address, a host name or a UNIX socket to bind to.
 *             verify_peer?: bool|Param, // Indicates if the peer should be verified in a TLS context.
 *             verify_host?: bool|Param, // Indicates if the host should exist as a certificate common name.
 *             cafile?: scalar|Param|null, // A certificate authority file.
 *             capath?: scalar|Param|null, // A directory that contains multiple certificate authority files.
 *             local_cert?: scalar|Param|null, // A PEM formatted certificate file.
 *             local_pk?: scalar|Param|null, // A private key file.
 *             passphrase?: scalar|Param|null, // The passphrase used to encrypt the "local_pk" file.
 *             ciphers?: scalar|Param|null, // A list of TLS ciphers separated by colons, commas or spaces (e.g. "RC3-SHA:TLS13-AES-128-GCM-SHA256"...)
 *             peer_fingerprint?: array{ // Associative array: hashing algorithm => hash(es).
 *                 sha1?: mixed,
 *                 pin-sha256?: mixed,
 *                 md5?: mixed,
 *             },
 *             crypto_method?: scalar|Param|null, // The minimum version of TLS to accept; must be one of STREAM_CRYPTO_METHOD_TLSv*_CLIENT constants.
 *             extra?: array<string, mixed>,
 *             rate_limiter?: scalar|Param|null, // Rate limiter name to use for throttling requests. // Default: null
 *             caching?: bool|array{ // Caching configuration.
 *                 enabled?: bool|Param, // Default: false
 *                 cache_pool?: string|Param, // The taggable cache pool to use for storing the responses. // Default: "cache.http_client"
 *                 shared?: bool|Param, // Indicates whether the cache is shared (public) or private. // Default: true
 *                 max_ttl?: int|Param, // The maximum TTL (in seconds) allowed for cached responses. Null means no cap. // Default: null
 *             },
 *             retry_failed?: bool|array{
 *                 enabled?: bool|Param, // Default: false
 *                 retry_strategy?: scalar|Param|null, // service id to override the retry strategy. // Default: null
 *                 http_codes?: array<string, array{ // Default: []
 *                     code?: int|Param,
 *                     methods?: list<string|Param>,
 *                 }>,
 *                 max_retries?: int|Param, // Default: 3
 *                 delay?: int|Param, // Time in ms to delay (or the initial value when multiplier is used). // Default: 1000
 *                 multiplier?: float|Param, // If greater than 1, delay will grow exponentially for each retry: delay * (multiple ^ retries). // Default: 2
 *                 max_delay?: int|Param, // Max time in ms that a retry should ever be delayed (0 = infinite). // Default: 0
 *                 jitter?: float|Param, // Randomness in percent (between 0 and 1) to apply to the delay. // Default: 0.1
 *             },
 *         },
 *         mock_response_factory?: scalar|Param|null, // The id of the service that should generate mock responses. It should be either an invokable or an iterable.
 *         scoped_clients?: array<string, string|array{ // Default: []
 *             scope?: scalar|Param|null, // The regular expression that the request URL must match before adding the other options. When none is provided, the base URI is used instead.
 *             base_uri?: scalar|Param|null, // The URI to resolve relative URLs, following rules in RFC 3985, section 2.
 *             auth_basic?: scalar|Param|null, // An HTTP Basic authentication "username:password".
 *             auth_bearer?: scalar|Param|null, // A token enabling HTTP Bearer authorization.
 *             auth_ntlm?: scalar|Param|null, // A "username:password" pair to use Microsoft NTLM authentication (requires the cURL extension).
 *             query?: array<string, scalar|Param|null>,
 *             headers?: array<string, mixed>,
 *             max_redirects?: int|Param, // The maximum number of redirects to follow.
 *             http_version?: scalar|Param|null, // The default HTTP version, typically 1.1 or 2.0, leave to null for the best version.
 *             resolve?: array<string, scalar|Param|null>,
 *             proxy?: scalar|Param|null, // The URL of the proxy to pass requests through or null for automatic detection.
 *             no_proxy?: scalar|Param|null, // A comma separated list of hosts that do not require a proxy to be reached.
 *             timeout?: float|Param, // The idle timeout, defaults to the "default_socket_timeout" ini parameter.
 *             max_duration?: float|Param, // The maximum execution time for the request+response as a whole.
 *             bindto?: scalar|Param|null, // A network interface name, IP address, a host name or a UNIX socket to bind to.
 *             verify_peer?: bool|Param, // Indicates if the peer should be verified in a TLS context.
 *             verify_host?: bool|Param, // Indicates if the host should exist as a certificate common name.
 *             cafile?: scalar|Param|null, // A certificate authority file.
 *             capath?: scalar|Param|null, // A directory that contains multiple certificate authority files.
 *             local_cert?: scalar|Param|null, // A PEM formatted certificate file.
 *             local_pk?: scalar|Param|null, // A private key file.
 *             passphrase?: scalar|Param|null, // The passphrase used to encrypt the "local_pk" file.
 *             ciphers?: scalar|Param|null, // A list of TLS ciphers separated by colons, commas or spaces (e.g. "RC3-SHA:TLS13-AES-128-GCM-SHA256"...).
 *             peer_fingerprint?: array{ // Associative array: hashing algorithm => hash(es).
 *                 sha1?: mixed,
 *                 pin-sha256?: mixed,
 *                 md5?: mixed,
 *             },
 *             crypto_method?: scalar|Param|null, // The minimum version of TLS to accept; must be one of STREAM_CRYPTO_METHOD_TLSv*_CLIENT constants.
 *             extra?: array<string, mixed>,
 *             rate_limiter?: scalar|Param|null, // Rate limiter name to use for throttling requests. // Default: null
 *             caching?: bool|array{ // Caching configuration.
 *                 enabled?: bool|Param, // Default: false
 *                 cache_pool?: string|Param, // The taggable cache pool to use for storing the responses. // Default: "cache.http_client"
 *                 shared?: bool|Param, // Indicates whether the cache is shared (public) or private. // Default: true
 *                 max_ttl?: int|Param, // The maximum TTL (in seconds) allowed for cached responses. Null means no cap. // Default: null
 *             },
 *             retry_failed?: bool|array{
 *                 enabled?: bool|Param, // Default: false
 *                 retry_strategy?: scalar|Param|null, // service id to override the retry strategy. // Default: null
 *                 http_codes?: array<string, array{ // Default: []
 *                     code?: int|Param,
 *                     methods?: list<string|Param>,
 *                 }>,
 *                 max_retries?: int|Param, // Default: 3
 *                 delay?: int|Param, // Time in ms to delay (or the initial value when multiplier is used). // Default: 1000
 *                 multiplier?: float|Param, // If greater than 1, delay will grow exponentially for each retry: delay * (multiple ^ retries). // Default: 2
 *                 max_delay?: int|Param, // Max time in ms that a retry should ever be delayed (0 = infinite). // Default: 0
 *                 jitter?: float|Param, // Randomness in percent (between 0 and 1) to apply to the delay. // Default: 0.1
 *             },
 *         }>,
 *     },
 *     mailer?: bool|array{ // Mailer configuration
 *         enabled?: bool|Param, // Default: true
 *         message_bus?: scalar|Param|null, // The message bus to use. Defaults to the default bus if the Messenger component is installed. // Default: null
 *         dsn?: scalar|Param|null, // Default: null
 *         transports?: array<string, scalar|Param|null>,
 *         envelope?: array{ // Mailer Envelope configuration
 *             sender?: scalar|Param|null,
 *             recipients?: list<scalar|Param|null>,
 *             allowed_recipients?: list<scalar|Param|null>,
 *         },
 *         headers?: array<string, string|array{ // Default: []
 *             value?: mixed,
 *         }>,
 *         dkim_signer?: bool|array{ // DKIM signer configuration
 *             enabled?: bool|Param, // Default: false
 *             key?: scalar|Param|null, // Key content, or path to key (in PEM format with the `file://` prefix) // Default: ""
 *             domain?: scalar|Param|null, // Default: ""
 *             select?: scalar|Param|null, // Default: ""
 *             passphrase?: scalar|Param|null, // The private key passphrase // Default: ""
 *             options?: array<string, mixed>,
 *         },
 *         smime_signer?: bool|array{ // S/MIME signer configuration
 *             enabled?: bool|Param, // Default: false
 *             key?: scalar|Param|null, // Path to key (in PEM format) // Default: ""
 *             certificate?: scalar|Param|null, // Path to certificate (in PEM format without the `file://` prefix) // Default: ""
 *             passphrase?: scalar|Param|null, // The private key passphrase // Default: null
 *             extra_certificates?: scalar|Param|null, // Default: null
 *             sign_options?: int|Param, // Default: null
 *         },
 *         smime_encrypter?: bool|array{ // S/MIME encrypter configuration
 *             enabled?: bool|Param, // Default: false
 *             repository?: scalar|Param|null, // S/MIME certificate repository service. This service shall implement the `Symfony\Component\Mailer\EventListener\SmimeCertificateRepositoryInterface`. // Default: ""
 *             cipher?: int|Param, // A set of algorithms used to encrypt the message // Default: null
 *         },
 *     },
 *     secrets?: bool|array{
 *         enabled?: bool|Param, // Default: true
 *         vault_directory?: scalar|Param|null, // Default: "%kernel.project_dir%/config/secrets/%kernel.runtime_environment%"
 *         local_dotenv_file?: scalar|Param|null, // Default: "%kernel.project_dir%/.env.%kernel.environment%.local"
 *         decryption_env_var?: scalar|Param|null, // Default: "base64:default::SYMFONY_DECRYPTION_SECRET"
 *     },
 *     notifier?: bool|array{ // Notifier configuration
 *         enabled?: bool|Param, // Default: false
 *         message_bus?: scalar|Param|null, // The message bus to use. Defaults to the default bus if the Messenger component is installed. // Default: null
 *         chatter_transports?: array<string, scalar|Param|null>,
 *         texter_transports?: array<string, scalar|Param|null>,
 *         notification_on_failed_messages?: bool|Param, // Default: false
 *         channel_policy?: array<string, string|list<scalar|Param|null>>,
 *         admin_recipients?: list<array{ // Default: []
 *             email?: scalar|Param|null,
 *             phone?: scalar|Param|null, // Default: ""
 *         }>,
 *     },
 *     rate_limiter?: bool|array{ // Rate limiter configuration
 *         enabled?: bool|Param, // Default: false
 *         limiters?: array<string, array{ // Default: []
 *             lock_factory?: scalar|Param|null, // The service ID of the lock factory used by this limiter (or null to disable locking). // Default: "auto"
 *             cache_pool?: scalar|Param|null, // The cache pool to use for storing the current limiter state. // Default: "cache.rate_limiter"
 *             storage_service?: scalar|Param|null, // The service ID of a custom storage implementation, this precedes any configured "cache_pool". // Default: null
 *             policy?: "fixed_window"|"token_bucket"|"sliding_window"|"compound"|"no_limit"|Param, // The algorithm to be used by this limiter.
 *             limiters?: list<scalar|Param|null>,
 *             limit?: int|Param, // The maximum allowed hits in a fixed interval or burst.
 *             interval?: scalar|Param|null, // Configures the fixed interval if "policy" is set to "fixed_window" or "sliding_window". The value must be a number followed by "second", "minute", "hour", "day", "week" or "month" (or their plural equivalent).
 *             rate?: array{ // Configures the fill rate if "policy" is set to "token_bucket".
 *                 interval?: scalar|Param|null, // Configures the rate interval. The value must be a number followed by "second", "minute", "hour", "day", "week" or "month" (or their plural equivalent).
 *                 amount?: int|Param, // Amount of tokens to add each interval. // Default: 1
 *             },
 *         }>,
 *     },
 *     uid?: bool|array{ // Uid configuration
 *         enabled?: bool|Param, // Default: false
 *         default_uuid_version?: 7|6|4|1|Param, // Default: 7
 *         name_based_uuid_version?: 5|3|Param, // Default: 5
 *         name_based_uuid_namespace?: scalar|Param|null,
 *         time_based_uuid_version?: 7|6|1|Param, // Default: 7
 *         time_based_uuid_node?: scalar|Param|null,
 *     },
 *     html_sanitizer?: bool|array{ // HtmlSanitizer configuration
 *         enabled?: bool|Param, // Default: false
 *         sanitizers?: array<string, array{ // Default: []
 *             allow_safe_elements?: bool|Param, // Allows "safe" elements and attributes. // Default: false
 *             allow_static_elements?: bool|Param, // Allows all static elements and attributes from the W3C Sanitizer API standard. // Default: false
 *             allow_elements?: array<string, mixed>,
 *             block_elements?: list<string|Param>,
 *             drop_elements?: list<string|Param>,
 *             allow_attributes?: array<string, mixed>,
 *             drop_attributes?: array<string, mixed>,
 *             force_attributes?: array<string, array<string, string|Param>>,
 *             force_https_urls?: bool|Param, // Transforms URLs using the HTTP scheme to use the HTTPS scheme instead. // Default: false
 *             allowed_link_schemes?: list<string|Param>,
 *             allowed_link_hosts?: list<string|Param>|null,
 *             allow_relative_links?: bool|Param, // Allows relative URLs to be used in links href attributes. // Default: false
 *             allowed_media_schemes?: list<string|Param>,
 *             allowed_media_hosts?: list<string|Param>|null,
 *             allow_relative_medias?: bool|Param, // Allows relative URLs to be used in media source attributes (img, audio, video, ...). // Default: false
 *             with_attribute_sanitizers?: list<string|Param>,
 *             without_attribute_sanitizers?: list<string|Param>,
 *             max_input_length?: int|Param, // The maximum length allowed for the sanitized input. // Default: 0
 *         }>,
 *     },
 *     webhook?: bool|array{ // Webhook configuration
 *         enabled?: bool|Param, // Default: false
 *         message_bus?: scalar|Param|null, // The message bus to use. // Default: "messenger.default_bus"
 *         routing?: array<string, array{ // Default: []
 *             service?: scalar|Param|null,
 *             secret?: scalar|Param|null, // Default: ""
 *         }>,
 *     },
 *     remote-event?: bool|array{ // RemoteEvent configuration
 *         enabled?: bool|Param, // Default: false
 *     },
 *     json_streamer?: bool|array{ // JSON streamer configuration
 *         enabled?: bool|Param, // Default: false
 *     },
 * }
 * @psalm-type DoctrineConfig = array{
 *     dbal?: array{
 *         default_connection?: scalar|Param|null,
 *         types?: array<string, string|array{ // Default: []
 *             class?: scalar|Param|null,
 *             commented?: bool|Param, // Deprecated: The doctrine-bundle type commenting features were removed; the corresponding config parameter was deprecated in 2.0 and will be dropped in 3.0.
 *         }>,
 *         driver_schemes?: array<string, scalar|Param|null>,
 *         connections?: array<string, array{ // Default: []
 *             url?: scalar|Param|null, // A URL with connection information; any parameter value parsed from this string will override explicitly set parameters
 *             dbname?: scalar|Param|null,
 *             host?: scalar|Param|null, // Defaults to "localhost" at runtime.
 *             port?: scalar|Param|null, // Defaults to null at runtime.
 *             user?: scalar|Param|null, // Defaults to "root" at runtime.
 *             password?: scalar|Param|null, // Defaults to null at runtime.
 *             override_url?: bool|Param, // Deprecated: The "doctrine.dbal.override_url" configuration key is deprecated.
 *             dbname_suffix?: scalar|Param|null, // Adds the given suffix to the configured database name, this option has no effects for the SQLite platform
 *             application_name?: scalar|Param|null,
 *             charset?: scalar|Param|null,
 *             path?: scalar|Param|null,
 *             memory?: bool|Param,
 *             unix_socket?: scalar|Param|null, // The unix socket to use for MySQL
 *             persistent?: bool|Param, // True to use as persistent connection for the ibm_db2 driver
 *             protocol?: scalar|Param|null, // The protocol to use for the ibm_db2 driver (default to TCPIP if omitted)
 *             service?: bool|Param, // True to use SERVICE_NAME as connection parameter instead of SID for Oracle
 *             servicename?: scalar|Param|null, // Overrules dbname parameter if given and used as SERVICE_NAME or SID connection parameter for Oracle depending on the service parameter.
 *             sessionMode?: scalar|Param|null, // The session mode to use for the oci8 driver
 *             server?: scalar|Param|null, // The name of a running database server to connect to for SQL Anywhere.
 *             default_dbname?: scalar|Param|null, // Override the default database (postgres) to connect to for PostgreSQL connexion.
 *             sslmode?: scalar|Param|null, // Determines whether or with what priority a SSL TCP/IP connection will be negotiated with the server for PostgreSQL.
 *             sslrootcert?: scalar|Param|null, // The name of a file containing SSL certificate authority (CA) certificate(s). If the file exists, the server's certificate will be verified to be signed by one of these authorities.
 *             sslcert?: scalar|Param|null, // The path to the SSL client certificate file for PostgreSQL.
 *             sslkey?: scalar|Param|null, // The path to the SSL client key file for PostgreSQL.
 *             sslcrl?: scalar|Param|null, // The file name of the SSL certificate revocation list for PostgreSQL.
 *             pooled?: bool|Param, // True to use a pooled server with the oci8/pdo_oracle driver
 *             MultipleActiveResultSets?: bool|Param, // Configuring MultipleActiveResultSets for the pdo_sqlsrv driver
 *             use_savepoints?: bool|Param, // Use savepoints for nested transactions
 *             instancename?: scalar|Param|null, // Optional parameter, complete whether to add the INSTANCE_NAME parameter in the connection. It is generally used to connect to an Oracle RAC server to select the name of a particular instance.
 *             connectstring?: scalar|Param|null, // Complete Easy Connect connection descriptor, see https://docs.oracle.com/database/121/NETAG/naming.htm.When using this option, you will still need to provide the user and password parameters, but the other parameters will no longer be used. Note that when using this parameter, the getHost and getPort methods from Doctrine\DBAL\Connection will no longer function as expected.
 *             driver?: scalar|Param|null, // Default: "pdo_mysql"
 *             platform_service?: scalar|Param|null, // Deprecated: The "platform_service" configuration key is deprecated since doctrine-bundle 2.9. DBAL 4 will not support setting a custom platform via connection params anymore.
 *             auto_commit?: bool|Param,
 *             schema_filter?: scalar|Param|null,
 *             logging?: bool|Param, // Default: true
 *             profiling?: bool|Param, // Default: true
 *             profiling_collect_backtrace?: bool|Param, // Enables collecting backtraces when profiling is enabled // Default: false
 *             profiling_collect_schema_errors?: bool|Param, // Enables collecting schema errors when profiling is enabled // Default: true
 *             disable_type_comments?: bool|Param,
 *             server_version?: scalar|Param|null,
 *             idle_connection_ttl?: int|Param, // Default: 600
 *             driver_class?: scalar|Param|null,
 *             wrapper_class?: scalar|Param|null,
 *             keep_slave?: bool|Param, // Deprecated: The "keep_slave" configuration key is deprecated since doctrine-bundle 2.2. Use the "keep_replica" configuration key instead.
 *             keep_replica?: bool|Param,
 *             options?: array<string, mixed>,
 *             mapping_types?: array<string, scalar|Param|null>,
 *             default_table_options?: array<string, scalar|Param|null>,
 *             schema_manager_factory?: scalar|Param|null, // Default: "doctrine.dbal.legacy_schema_manager_factory"
 *             result_cache?: scalar|Param|null,
 *             slaves?: array<string, array{ // Default: []
 *                 url?: scalar|Param|null, // A URL with connection information; any parameter value parsed from this string will override explicitly set parameters
 *                 dbname?: scalar|Param|null,
 *                 host?: scalar|Param|null, // Defaults to "localhost" at runtime.
 *                 port?: scalar|Param|null, // Defaults to null at runtime.
 *                 user?: scalar|Param|null, // Defaults to "root" at runtime.
 *                 password?: scalar|Param|null, // Defaults to null at runtime.
 *                 override_url?: bool|Param, // Deprecated: The "doctrine.dbal.override_url" configuration key is deprecated.
 *                 dbname_suffix?: scalar|Param|null, // Adds the given suffix to the configured database name, this option has no effects for the SQLite platform
 *                 application_name?: scalar|Param|null,
 *                 charset?: scalar|Param|null,
 *                 path?: scalar|Param|null,
 *                 memory?: bool|Param,
 *                 unix_socket?: scalar|Param|null, // The unix socket to use for MySQL
 *                 persistent?: bool|Param, // True to use as persistent connection for the ibm_db2 driver
 *                 protocol?: scalar|Param|null, // The protocol to use for the ibm_db2 driver (default to TCPIP if omitted)
 *                 service?: bool|Param, // True to use SERVICE_NAME as connection parameter instead of SID for Oracle
 *                 servicename?: scalar|Param|null, // Overrules dbname parameter if given and used as SERVICE_NAME or SID connection parameter for Oracle depending on the service parameter.
 *                 sessionMode?: scalar|Param|null, // The session mode to use for the oci8 driver
 *                 server?: scalar|Param|null, // The name of a running database server to connect to for SQL Anywhere.
 *                 default_dbname?: scalar|Param|null, // Override the default database (postgres) to connect to for PostgreSQL connexion.
 *                 sslmode?: scalar|Param|null, // Determines whether or with what priority a SSL TCP/IP connection will be negotiated with the server for PostgreSQL.
 *                 sslrootcert?: scalar|Param|null, // The name of a file containing SSL certificate authority (CA) certificate(s). If the file exists, the server's certificate will be verified to be signed by one of these authorities.
 *                 sslcert?: scalar|Param|null, // The path to the SSL client certificate file for PostgreSQL.
 *                 sslkey?: scalar|Param|null, // The path to the SSL client key file for PostgreSQL.
 *                 sslcrl?: scalar|Param|null, // The file name of the SSL certificate revocation list for PostgreSQL.
 *                 pooled?: bool|Param, // True to use a pooled server with the oci8/pdo_oracle driver
 *                 MultipleActiveResultSets?: bool|Param, // Configuring MultipleActiveResultSets for the pdo_sqlsrv driver
 *                 use_savepoints?: bool|Param, // Use savepoints for nested transactions
 *                 instancename?: scalar|Param|null, // Optional parameter, complete whether to add the INSTANCE_NAME parameter in the connection. It is generally used to connect to an Oracle RAC server to select the name of a particular instance.
 *                 connectstring?: scalar|Param|null, // Complete Easy Connect connection descriptor, see https://docs.oracle.com/database/121/NETAG/naming.htm.When using this option, you will still need to provide the user and password parameters, but the other parameters will no longer be used. Note that when using this parameter, the getHost and getPort methods from Doctrine\DBAL\Connection will no longer function as expected.
 *             }>,
 *             replicas?: array<string, array{ // Default: []
 *                 url?: scalar|Param|null, // A URL with connection information; any parameter value parsed from this string will override explicitly set parameters
 *                 dbname?: scalar|Param|null,
 *                 host?: scalar|Param|null, // Defaults to "localhost" at runtime.
 *                 port?: scalar|Param|null, // Defaults to null at runtime.
 *                 user?: scalar|Param|null, // Defaults to "root" at runtime.
 *                 password?: scalar|Param|null, // Defaults to null at runtime.
 *                 override_url?: bool|Param, // Deprecated: The "doctrine.dbal.override_url" configuration key is deprecated.
 *                 dbname_suffix?: scalar|Param|null, // Adds the given suffix to the configured database name, this option has no effects for the SQLite platform
 *                 application_name?: scalar|Param|null,
 *                 charset?: scalar|Param|null,
 *                 path?: scalar|Param|null,
 *                 memory?: bool|Param,
 *                 unix_socket?: scalar|Param|null, // The unix socket to use for MySQL
 *                 persistent?: bool|Param, // True to use as persistent connection for the ibm_db2 driver
 *                 protocol?: scalar|Param|null, // The protocol to use for the ibm_db2 driver (default to TCPIP if omitted)
 *                 service?: bool|Param, // True to use SERVICE_NAME as connection parameter instead of SID for Oracle
 *                 servicename?: scalar|Param|null, // Overrules dbname parameter if given and used as SERVICE_NAME or SID connection parameter for Oracle depending on the service parameter.
 *                 sessionMode?: scalar|Param|null, // The session mode to use for the oci8 driver
 *                 server?: scalar|Param|null, // The name of a running database server to connect to for SQL Anywhere.
 *                 default_dbname?: scalar|Param|null, // Override the default database (postgres) to connect to for PostgreSQL connexion.
 *                 sslmode?: scalar|Param|null, // Determines whether or with what priority a SSL TCP/IP connection will be negotiated with the server for PostgreSQL.
 *                 sslrootcert?: scalar|Param|null, // The name of a file containing SSL certificate authority (CA) certificate(s). If the file exists, the server's certificate will be verified to be signed by one of these authorities.
 *                 sslcert?: scalar|Param|null, // The path to the SSL client certificate file for PostgreSQL.
 *                 sslkey?: scalar|Param|null, // The path to the SSL client key file for PostgreSQL.
 *                 sslcrl?: scalar|Param|null, // The file name of the SSL certificate revocation list for PostgreSQL.
 *                 pooled?: bool|Param, // True to use a pooled server with the oci8/pdo_oracle driver
 *                 MultipleActiveResultSets?: bool|Param, // Configuring MultipleActiveResultSets for the pdo_sqlsrv driver
 *                 use_savepoints?: bool|Param, // Use savepoints for nested transactions
 *                 instancename?: scalar|Param|null, // Optional parameter, complete whether to add the INSTANCE_NAME parameter in the connection. It is generally used to connect to an Oracle RAC server to select the name of a particular instance.
 *                 connectstring?: scalar|Param|null, // Complete Easy Connect connection descriptor, see https://docs.oracle.com/database/121/NETAG/naming.htm.When using this option, you will still need to provide the user and password parameters, but the other parameters will no longer be used. Note that when using this parameter, the getHost and getPort methods from Doctrine\DBAL\Connection will no longer function as expected.
 *             }>,
 *         }>,
 *     },
 *     orm?: array{
 *         default_entity_manager?: scalar|Param|null,
 *         auto_generate_proxy_classes?: scalar|Param|null, // Auto generate mode possible values are: "NEVER", "ALWAYS", "FILE_NOT_EXISTS", "EVAL", "FILE_NOT_EXISTS_OR_CHANGED", this option is ignored when the "enable_native_lazy_objects" option is true // Default: false
 *         enable_lazy_ghost_objects?: bool|Param, // Enables the new implementation of proxies based on lazy ghosts instead of using the legacy implementation // Default: false
 *         enable_native_lazy_objects?: bool|Param, // Enables the new native implementation of PHP lazy objects instead of generated proxies // Default: false
 *         proxy_dir?: scalar|Param|null, // Configures the path where generated proxy classes are saved when using non-native lazy objects, this option is ignored when the "enable_native_lazy_objects" option is true // Default: "%kernel.build_dir%/doctrine/orm/Proxies"
 *         proxy_namespace?: scalar|Param|null, // Defines the root namespace for generated proxy classes when using non-native lazy objects, this option is ignored when the "enable_native_lazy_objects" option is true // Default: "Proxies"
 *         controller_resolver?: bool|array{
 *             enabled?: bool|Param, // Default: true
 *             auto_mapping?: bool|Param|null, // Set to false to disable using route placeholders as lookup criteria when the primary key doesn't match the argument name // Default: null
 *             evict_cache?: bool|Param, // Set to true to fetch the entity from the database instead of using the cache, if any // Default: false
 *         },
 *         entity_managers?: array<string, array{ // Default: []
 *             query_cache_driver?: string|array{
 *                 type?: scalar|Param|null, // Default: null
 *                 id?: scalar|Param|null,
 *                 pool?: scalar|Param|null,
 *             },
 *             metadata_cache_driver?: string|array{
 *                 type?: scalar|Param|null, // Default: null
 *                 id?: scalar|Param|null,
 *                 pool?: scalar|Param|null,
 *             },
 *             result_cache_driver?: string|array{
 *                 type?: scalar|Param|null, // Default: null
 *                 id?: scalar|Param|null,
 *                 pool?: scalar|Param|null,
 *             },
 *             entity_listeners?: array{
 *                 entities?: array<string, array{ // Default: []
 *                     listeners?: array<string, array{ // Default: []
 *                         events?: list<array{ // Default: []
 *                             type?: scalar|Param|null,
 *                             method?: scalar|Param|null, // Default: null
 *                         }>,
 *                     }>,
 *                 }>,
 *             },
 *             connection?: scalar|Param|null,
 *             class_metadata_factory_name?: scalar|Param|null, // Default: "Doctrine\\ORM\\Mapping\\ClassMetadataFactory"
 *             default_repository_class?: scalar|Param|null, // Default: "Doctrine\\ORM\\EntityRepository"
 *             auto_mapping?: scalar|Param|null, // Default: false
 *             naming_strategy?: scalar|Param|null, // Default: "doctrine.orm.naming_strategy.default"
 *             quote_strategy?: scalar|Param|null, // Default: "doctrine.orm.quote_strategy.default"
 *             typed_field_mapper?: scalar|Param|null, // Default: "doctrine.orm.typed_field_mapper.default"
 *             entity_listener_resolver?: scalar|Param|null, // Default: null
 *             fetch_mode_subselect_batch_size?: scalar|Param|null,
 *             repository_factory?: scalar|Param|null, // Default: "doctrine.orm.container_repository_factory"
 *             schema_ignore_classes?: list<scalar|Param|null>,
 *             report_fields_where_declared?: bool|Param, // Set to "true" to opt-in to the new mapping driver mode that was added in Doctrine ORM 2.16 and will be mandatory in ORM 3.0. See https://github.com/doctrine/orm/pull/10455. // Default: false
 *             validate_xml_mapping?: bool|Param, // Set to "true" to opt-in to the new mapping driver mode that was added in Doctrine ORM 2.14. See https://github.com/doctrine/orm/pull/6728. // Default: false
 *             second_level_cache?: array{
 *                 region_cache_driver?: string|array{
 *                     type?: scalar|Param|null, // Default: null
 *                     id?: scalar|Param|null,
 *                     pool?: scalar|Param|null,
 *                 },
 *                 region_lock_lifetime?: scalar|Param|null, // Default: 60
 *                 log_enabled?: bool|Param, // Default: true
 *                 region_lifetime?: scalar|Param|null, // Default: 3600
 *                 enabled?: bool|Param, // Default: true
 *                 factory?: scalar|Param|null,
 *                 regions?: array<string, array{ // Default: []
 *                     cache_driver?: string|array{
 *                         type?: scalar|Param|null, // Default: null
 *                         id?: scalar|Param|null,
 *                         pool?: scalar|Param|null,
 *                     },
 *                     lock_path?: scalar|Param|null, // Default: "%kernel.cache_dir%/doctrine/orm/slc/filelock"
 *                     lock_lifetime?: scalar|Param|null, // Default: 60
 *                     type?: scalar|Param|null, // Default: "default"
 *                     lifetime?: scalar|Param|null, // Default: 0
 *                     service?: scalar|Param|null,
 *                     name?: scalar|Param|null,
 *                 }>,
 *                 loggers?: array<string, array{ // Default: []
 *                     name?: scalar|Param|null,
 *                     service?: scalar|Param|null,
 *                 }>,
 *             },
 *             hydrators?: array<string, scalar|Param|null>,
 *             mappings?: array<string, bool|string|array{ // Default: []
 *                 mapping?: scalar|Param|null, // Default: true
 *                 type?: scalar|Param|null,
 *                 dir?: scalar|Param|null,
 *                 alias?: scalar|Param|null,
 *                 prefix?: scalar|Param|null,
 *                 is_bundle?: bool|Param,
 *             }>,
 *             dql?: array{
 *                 string_functions?: array<string, scalar|Param|null>,
 *                 numeric_functions?: array<string, scalar|Param|null>,
 *                 datetime_functions?: array<string, scalar|Param|null>,
 *             },
 *             filters?: array<string, string|array{ // Default: []
 *                 class?: scalar|Param|null,
 *                 enabled?: bool|Param, // Default: false
 *                 parameters?: array<string, mixed>,
 *             }>,
 *             identity_generation_preferences?: array<string, scalar|Param|null>,
 *         }>,
 *         resolve_target_entities?: array<string, scalar|Param|null>,
 *     },
 * }
 * @psalm-type DoctrineMigrationsConfig = array{
 *     enable_service_migrations?: bool|Param, // Whether to enable fetching migrations from the service container. // Default: false
 *     migrations_paths?: array<string, scalar|Param|null>,
 *     services?: array<string, scalar|Param|null>,
 *     factories?: array<string, scalar|Param|null>,
 *     storage?: array{ // Storage to use for migration status metadata.
 *         table_storage?: array{ // The default metadata storage, implemented as a table in the database.
 *             table_name?: scalar|Param|null, // Default: null
 *             version_column_name?: scalar|Param|null, // Default: null
 *             version_column_length?: scalar|Param|null, // Default: null
 *             executed_at_column_name?: scalar|Param|null, // Default: null
 *             execution_time_column_name?: scalar|Param|null, // Default: null
 *         },
 *     },
 *     migrations?: list<scalar|Param|null>,
 *     connection?: scalar|Param|null, // Connection name to use for the migrations database. // Default: null
 *     em?: scalar|Param|null, // Entity manager name to use for the migrations database (available when doctrine/orm is installed). // Default: null
 *     all_or_nothing?: scalar|Param|null, // Run all migrations in a transaction. // Default: false
 *     check_database_platform?: scalar|Param|null, // Adds an extra check in the generated migrations to allow execution only on the same platform as they were initially generated on. // Default: true
 *     custom_template?: scalar|Param|null, // Custom template path for generated migration classes. // Default: null
 *     organize_migrations?: scalar|Param|null, // Organize migrations mode. Possible values are: "BY_YEAR", "BY_YEAR_AND_MONTH", false // Default: false
 *     enable_profiler?: bool|Param, // Whether or not to enable the profiler collector to calculate and visualize migration status. This adds some queries overhead. // Default: false
 *     transactional?: bool|Param, // Whether or not to wrap migrations in a single transaction. // Default: true
 * }
 * @psalm-type SecurityConfig = array{
 *     access_denied_url?: scalar|Param|null, // Default: null
 *     session_fixation_strategy?: "none"|"migrate"|"invalidate"|Param, // Default: "migrate"
 *     hide_user_not_found?: bool|Param, // Deprecated: The "hide_user_not_found" option is deprecated and will be removed in 8.0. Use the "expose_security_errors" option instead.
 *     expose_security_errors?: \Symfony\Component\Security\Http\Authentication\ExposeSecurityLevel::None|\Symfony\Component\Security\Http\Authentication\ExposeSecurityLevel::AccountStatus|\Symfony\Component\Security\Http\Authentication\ExposeSecurityLevel::All|Param, // Default: "none"
 *     erase_credentials?: bool|Param, // Default: true
 *     access_decision_manager?: array{
 *         strategy?: "affirmative"|"consensus"|"unanimous"|"priority"|Param,
 *         service?: scalar|Param|null,
 *         strategy_service?: scalar|Param|null,
 *         allow_if_all_abstain?: bool|Param, // Default: false
 *         allow_if_equal_granted_denied?: bool|Param, // Default: true
 *     },
 *     password_hashers?: array<string, string|array{ // Default: []
 *         algorithm?: scalar|Param|null,
 *         migrate_from?: list<scalar|Param|null>,
 *         hash_algorithm?: scalar|Param|null, // Name of hashing algorithm for PBKDF2 (i.e. sha256, sha512, etc..) See hash_algos() for a list of supported algorithms. // Default: "sha512"
 *         key_length?: scalar|Param|null, // Default: 40
 *         ignore_case?: bool|Param, // Default: false
 *         encode_as_base64?: bool|Param, // Default: true
 *         iterations?: scalar|Param|null, // Default: 5000
 *         cost?: int|Param, // Default: null
 *         memory_cost?: scalar|Param|null, // Default: null
 *         time_cost?: scalar|Param|null, // Default: null
 *         id?: scalar|Param|null,
 *     }>,
 *     providers?: array<string, array{ // Default: []
 *         id?: scalar|Param|null,
 *         chain?: array{
 *             providers?: list<scalar|Param|null>,
 *         },
 *         entity?: array{
 *             class?: scalar|Param|null, // The full entity class name of your user class.
 *             property?: scalar|Param|null, // Default: null
 *             manager_name?: scalar|Param|null, // Default: null
 *         },
 *         memory?: array{
 *             users?: array<string, array{ // Default: []
 *                 password?: scalar|Param|null, // Default: null
 *                 roles?: list<scalar|Param|null>,
 *             }>,
 *         },
 *         ldap?: array{
 *             service?: scalar|Param|null,
 *             base_dn?: scalar|Param|null,
 *             search_dn?: scalar|Param|null, // Default: null
 *             search_password?: scalar|Param|null, // Default: null
 *             extra_fields?: list<scalar|Param|null>,
 *             default_roles?: list<scalar|Param|null>,
 *             role_fetcher?: scalar|Param|null, // Default: null
 *             uid_key?: scalar|Param|null, // Default: "sAMAccountName"
 *             filter?: scalar|Param|null, // Default: "({uid_key}={user_identifier})"
 *             password_attribute?: scalar|Param|null, // Default: null
 *         },
 *     }>,
 *     firewalls?: array<string, array{ // Default: []
 *         pattern?: scalar|Param|null,
 *         host?: scalar|Param|null,
 *         methods?: list<scalar|Param|null>,
 *         security?: bool|Param, // Default: true
 *         user_checker?: scalar|Param|null, // The UserChecker to use when authenticating users in this firewall. // Default: "security.user_checker"
 *         request_matcher?: scalar|Param|null,
 *         access_denied_url?: scalar|Param|null,
 *         access_denied_handler?: scalar|Param|null,
 *         entry_point?: scalar|Param|null, // An enabled authenticator name or a service id that implements "Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface".
 *         provider?: scalar|Param|null,
 *         stateless?: bool|Param, // Default: false
 *         lazy?: bool|Param, // Default: false
 *         context?: scalar|Param|null,
 *         logout?: array{
 *             enable_csrf?: bool|Param|null, // Default: null
 *             csrf_token_id?: scalar|Param|null, // Default: "logout"
 *             csrf_parameter?: scalar|Param|null, // Default: "_csrf_token"
 *             csrf_token_manager?: scalar|Param|null,
 *             path?: scalar|Param|null, // Default: "/logout"
 *             target?: scalar|Param|null, // Default: "/"
 *             invalidate_session?: bool|Param, // Default: true
 *             clear_site_data?: list<"*"|"cache"|"cookies"|"storage"|"executionContexts"|Param>,
 *             delete_cookies?: array<string, array{ // Default: []
 *                 path?: scalar|Param|null, // Default: null
 *                 domain?: scalar|Param|null, // Default: null
 *                 secure?: scalar|Param|null, // Default: false
 *                 samesite?: scalar|Param|null, // Default: null
 *                 partitioned?: scalar|Param|null, // Default: false
 *             }>,
 *         },
 *         switch_user?: array{
 *             provider?: scalar|Param|null,
 *             parameter?: scalar|Param|null, // Default: "_switch_user"
 *             role?: scalar|Param|null, // Default: "ROLE_ALLOWED_TO_SWITCH"
 *             target_route?: scalar|Param|null, // Default: null
 *         },
 *         required_badges?: list<scalar|Param|null>,
 *         custom_authenticators?: list<scalar|Param|null>,
 *         login_throttling?: array{
 *             limiter?: scalar|Param|null, // A service id implementing "Symfony\Component\HttpFoundation\RateLimiter\RequestRateLimiterInterface".
 *             max_attempts?: int|Param, // Default: 5
 *             interval?: scalar|Param|null, // Default: "1 minute"
 *             lock_factory?: scalar|Param|null, // The service ID of the lock factory used by the login rate limiter (or null to disable locking). // Default: null
 *             cache_pool?: string|Param, // The cache pool to use for storing the limiter state // Default: "cache.rate_limiter"
 *             storage_service?: string|Param, // The service ID of a custom storage implementation, this precedes any configured "cache_pool" // Default: null
 *         },
 *         x509?: array{
 *             provider?: scalar|Param|null,
 *             user?: scalar|Param|null, // Default: "SSL_CLIENT_S_DN_Email"
 *             credentials?: scalar|Param|null, // Default: "SSL_CLIENT_S_DN"
 *             user_identifier?: scalar|Param|null, // Default: "emailAddress"
 *         },
 *         remote_user?: array{
 *             provider?: scalar|Param|null,
 *             user?: scalar|Param|null, // Default: "REMOTE_USER"
 *         },
 *         login_link?: array{
 *             check_route?: scalar|Param|null, // Route that will validate the login link - e.g. "app_login_link_verify".
 *             check_post_only?: scalar|Param|null, // If true, only HTTP POST requests to "check_route" will be handled by the authenticator. // Default: false
 *             signature_properties?: list<scalar|Param|null>,
 *             lifetime?: int|Param, // The lifetime of the login link in seconds. // Default: 600
 *             max_uses?: int|Param, // Max number of times a login link can be used - null means unlimited within lifetime. // Default: null
 *             used_link_cache?: scalar|Param|null, // Cache service id used to expired links of max_uses is set.
 *             success_handler?: scalar|Param|null, // A service id that implements Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface.
 *             failure_handler?: scalar|Param|null, // A service id that implements Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface.
 *             provider?: scalar|Param|null, // The user provider to load users from.
 *             secret?: scalar|Param|null, // Default: "%kernel.secret%"
 *             always_use_default_target_path?: bool|Param, // Default: false
 *             default_target_path?: scalar|Param|null, // Default: "/"
 *             login_path?: scalar|Param|null, // Default: "/login"
 *             target_path_parameter?: scalar|Param|null, // Default: "_target_path"
 *             use_referer?: bool|Param, // Default: false
 *             failure_path?: scalar|Param|null, // Default: null
 *             failure_forward?: bool|Param, // Default: false
 *             failure_path_parameter?: scalar|Param|null, // Default: "_failure_path"
 *         },
 *         form_login?: array{
 *             provider?: scalar|Param|null,
 *             remember_me?: bool|Param, // Default: true
 *             success_handler?: scalar|Param|null,
 *             failure_handler?: scalar|Param|null,
 *             check_path?: scalar|Param|null, // Default: "/login_check"
 *             use_forward?: bool|Param, // Default: false
 *             login_path?: scalar|Param|null, // Default: "/login"
 *             username_parameter?: scalar|Param|null, // Default: "_username"
 *             password_parameter?: scalar|Param|null, // Default: "_password"
 *             csrf_parameter?: scalar|Param|null, // Default: "_csrf_token"
 *             csrf_token_id?: scalar|Param|null, // Default: "authenticate"
 *             enable_csrf?: bool|Param, // Default: false
 *             post_only?: bool|Param, // Default: true
 *             form_only?: bool|Param, // Default: false
 *             always_use_default_target_path?: bool|Param, // Default: false
 *             default_target_path?: scalar|Param|null, // Default: "/"
 *             target_path_parameter?: scalar|Param|null, // Default: "_target_path"
 *             use_referer?: bool|Param, // Default: false
 *             failure_path?: scalar|Param|null, // Default: null
 *             failure_forward?: bool|Param, // Default: false
 *             failure_path_parameter?: scalar|Param|null, // Default: "_failure_path"
 *         },
 *         form_login_ldap?: array{
 *             provider?: scalar|Param|null,
 *             remember_me?: bool|Param, // Default: true
 *             success_handler?: scalar|Param|null,
 *             failure_handler?: scalar|Param|null,
 *             check_path?: scalar|Param|null, // Default: "/login_check"
 *             use_forward?: bool|Param, // Default: false
 *             login_path?: scalar|Param|null, // Default: "/login"
 *             username_parameter?: scalar|Param|null, // Default: "_username"
 *             password_parameter?: scalar|Param|null, // Default: "_password"
 *             csrf_parameter?: scalar|Param|null, // Default: "_csrf_token"
 *             csrf_token_id?: scalar|Param|null, // Default: "authenticate"
 *             enable_csrf?: bool|Param, // Default: false
 *             post_only?: bool|Param, // Default: true
 *             form_only?: bool|Param, // Default: false
 *             always_use_default_target_path?: bool|Param, // Default: false
 *             default_target_path?: scalar|Param|null, // Default: "/"
 *             target_path_parameter?: scalar|Param|null, // Default: "_target_path"
 *             use_referer?: bool|Param, // Default: false
 *             failure_path?: scalar|Param|null, // Default: null
 *             failure_forward?: bool|Param, // Default: false
 *             failure_path_parameter?: scalar|Param|null, // Default: "_failure_path"
 *             service?: scalar|Param|null, // Default: "ldap"
 *             dn_string?: scalar|Param|null, // Default: "{user_identifier}"
 *             query_string?: scalar|Param|null,
 *             search_dn?: scalar|Param|null, // Default: ""
 *             search_password?: scalar|Param|null, // Default: ""
 *         },
 *         json_login?: array{
 *             provider?: scalar|Param|null,
 *             remember_me?: bool|Param, // Default: true
 *             success_handler?: scalar|Param|null,
 *             failure_handler?: scalar|Param|null,
 *             check_path?: scalar|Param|null, // Default: "/login_check"
 *             use_forward?: bool|Param, // Default: false
 *             login_path?: scalar|Param|null, // Default: "/login"
 *             username_path?: scalar|Param|null, // Default: "username"
 *             password_path?: scalar|Param|null, // Default: "password"
 *         },
 *         json_login_ldap?: array{
 *             provider?: scalar|Param|null,
 *             remember_me?: bool|Param, // Default: true
 *             success_handler?: scalar|Param|null,
 *             failure_handler?: scalar|Param|null,
 *             check_path?: scalar|Param|null, // Default: "/login_check"
 *             use_forward?: bool|Param, // Default: false
 *             login_path?: scalar|Param|null, // Default: "/login"
 *             username_path?: scalar|Param|null, // Default: "username"
 *             password_path?: scalar|Param|null, // Default: "password"
 *             service?: scalar|Param|null, // Default: "ldap"
 *             dn_string?: scalar|Param|null, // Default: "{user_identifier}"
 *             query_string?: scalar|Param|null,
 *             search_dn?: scalar|Param|null, // Default: ""
 *             search_password?: scalar|Param|null, // Default: ""
 *         },
 *         access_token?: array{
 *             provider?: scalar|Param|null,
 *             remember_me?: bool|Param, // Default: true
 *             success_handler?: scalar|Param|null,
 *             failure_handler?: scalar|Param|null,
 *             realm?: scalar|Param|null, // Default: null
 *             token_extractors?: list<scalar|Param|null>,
 *             token_handler?: string|array{
 *                 id?: scalar|Param|null,
 *                 oidc_user_info?: string|array{
 *                     base_uri?: scalar|Param|null, // Base URI of the userinfo endpoint on the OIDC server, or the OIDC server URI to use the discovery (require "discovery" to be configured).
 *                     discovery?: array{ // Enable the OIDC discovery.
 *                         cache?: array{
 *                             id?: scalar|Param|null, // Cache service id to use to cache the OIDC discovery configuration.
 *                         },
 *                     },
 *                     claim?: scalar|Param|null, // Claim which contains the user identifier (e.g. sub, email, etc.). // Default: "sub"
 *                     client?: scalar|Param|null, // HttpClient service id to use to call the OIDC server.
 *                 },
 *                 oidc?: array{
 *                     discovery?: array{ // Enable the OIDC discovery.
 *                         base_uri?: list<scalar|Param|null>,
 *                         cache?: array{
 *                             id?: scalar|Param|null, // Cache service id to use to cache the OIDC discovery configuration.
 *                         },
 *                     },
 *                     claim?: scalar|Param|null, // Claim which contains the user identifier (e.g.: sub, email..). // Default: "sub"
 *                     audience?: scalar|Param|null, // Audience set in the token, for validation purpose.
 *                     issuers?: list<scalar|Param|null>,
 *                     algorithm?: array<mixed>,
 *                     algorithms?: list<scalar|Param|null>,
 *                     key?: scalar|Param|null, // Deprecated: The "key" option is deprecated and will be removed in 8.0. Use the "keyset" option instead. // JSON-encoded JWK used to sign the token (must contain a "kty" key).
 *                     keyset?: scalar|Param|null, // JSON-encoded JWKSet used to sign the token (must contain a list of valid public keys).
 *                     encryption?: bool|array{
 *                         enabled?: bool|Param, // Default: false
 *                         enforce?: bool|Param, // When enabled, the token shall be encrypted. // Default: false
 *                         algorithms?: list<scalar|Param|null>,
 *                         keyset?: scalar|Param|null, // JSON-encoded JWKSet used to decrypt the token (must contain a list of valid private keys).
 *                     },
 *                 },
 *                 cas?: array{
 *                     validation_url?: scalar|Param|null, // CAS server validation URL
 *                     prefix?: scalar|Param|null, // CAS prefix // Default: "cas"
 *                     http_client?: scalar|Param|null, // HTTP Client service // Default: null
 *                 },
 *                 oauth2?: scalar|Param|null,
 *             },
 *         },
 *         http_basic?: array{
 *             provider?: scalar|Param|null,
 *             realm?: scalar|Param|null, // Default: "Secured Area"
 *         },
 *         http_basic_ldap?: array{
 *             provider?: scalar|Param|null,
 *             realm?: scalar|Param|null, // Default: "Secured Area"
 *             service?: scalar|Param|null, // Default: "ldap"
 *             dn_string?: scalar|Param|null, // Default: "{user_identifier}"
 *             query_string?: scalar|Param|null,
 *             search_dn?: scalar|Param|null, // Default: ""
 *             search_password?: scalar|Param|null, // Default: ""
 *         },
 *         remember_me?: array{
 *             secret?: scalar|Param|null, // Default: "%kernel.secret%"
 *             service?: scalar|Param|null,
 *             user_providers?: list<scalar|Param|null>,
 *             catch_exceptions?: bool|Param, // Default: true
 *             signature_properties?: list<scalar|Param|null>,
 *             token_provider?: string|array{
 *                 service?: scalar|Param|null, // The service ID of a custom remember-me token provider.
 *                 doctrine?: bool|array{
 *                     enabled?: bool|Param, // Default: false
 *                     connection?: scalar|Param|null, // Default: null
 *                 },
 *             },
 *             token_verifier?: scalar|Param|null, // The service ID of a custom rememberme token verifier.
 *             name?: scalar|Param|null, // Default: "REMEMBERME"
 *             lifetime?: int|Param, // Default: 31536000
 *             path?: scalar|Param|null, // Default: "/"
 *             domain?: scalar|Param|null, // Default: null
 *             secure?: true|false|"auto"|Param, // Default: null
 *             httponly?: bool|Param, // Default: true
 *             samesite?: null|"lax"|"strict"|"none"|Param, // Default: "lax"
 *             always_remember_me?: bool|Param, // Default: false
 *             remember_me_parameter?: scalar|Param|null, // Default: "_remember_me"
 *         },
 *     }>,
 *     access_control?: list<array{ // Default: []
 *         request_matcher?: scalar|Param|null, // Default: null
 *         requires_channel?: scalar|Param|null, // Default: null
 *         path?: scalar|Param|null, // Use the urldecoded format. // Default: null
 *         host?: scalar|Param|null, // Default: null
 *         port?: int|Param, // Default: null
 *         ips?: list<scalar|Param|null>,
 *         attributes?: array<string, scalar|Param|null>,
 *         route?: scalar|Param|null, // Default: null
 *         methods?: list<scalar|Param|null>,
 *         allow_if?: scalar|Param|null, // Default: null
 *         roles?: list<scalar|Param|null>,
 *     }>,
 *     role_hierarchy?: array<string, string|list<scalar|Param|null>>,
 * }
 * @psalm-type TwigConfig = array{
 *     form_themes?: list<scalar|Param|null>,
 *     globals?: array<string, array{ // Default: []
 *         id?: scalar|Param|null,
 *         type?: scalar|Param|null,
 *         value?: mixed,
 *     }>,
 *     autoescape_service?: scalar|Param|null, // Default: null
 *     autoescape_service_method?: scalar|Param|null, // Default: null
 *     base_template_class?: scalar|Param|null, // Deprecated: The child node "base_template_class" at path "twig.base_template_class" is deprecated.
 *     cache?: scalar|Param|null, // Default: true
 *     charset?: scalar|Param|null, // Default: "%kernel.charset%"
 *     debug?: bool|Param, // Default: "%kernel.debug%"
 *     strict_variables?: bool|Param, // Default: "%kernel.debug%"
 *     auto_reload?: scalar|Param|null,
 *     optimizations?: int|Param,
 *     default_path?: scalar|Param|null, // The default path used to load templates. // Default: "%kernel.project_dir%/templates"
 *     file_name_pattern?: list<scalar|Param|null>,
 *     paths?: array<string, mixed>,
 *     date?: array{ // The default format options used by the date filter.
 *         format?: scalar|Param|null, // Default: "F j, Y H:i"
 *         interval_format?: scalar|Param|null, // Default: "%d days"
 *         timezone?: scalar|Param|null, // The timezone used when formatting dates, when set to null, the timezone returned by date_default_timezone_get() is used. // Default: null
 *     },
 *     number_format?: array{ // The default format options for the number_format filter.
 *         decimals?: int|Param, // Default: 0
 *         decimal_point?: scalar|Param|null, // Default: "."
 *         thousands_separator?: scalar|Param|null, // Default: ","
 *     },
 *     mailer?: array{
 *         html_to_text_converter?: scalar|Param|null, // A service implementing the "Symfony\Component\Mime\HtmlToTextConverter\HtmlToTextConverterInterface". // Default: null
 *     },
 * }
 * @psalm-type WebProfilerConfig = array{
 *     toolbar?: bool|array{ // Profiler toolbar configuration
 *         enabled?: bool|Param, // Default: false
 *         ajax_replace?: bool|Param, // Replace toolbar on AJAX requests // Default: false
 *     },
 *     intercept_redirects?: bool|Param, // Default: false
 *     excluded_ajax_paths?: scalar|Param|null, // Default: "^/((index|app(_[\\w]+)?)\\.php/)?_wdt"
 * }
 * @psalm-type MonologConfig = array{
 *     use_microseconds?: scalar|Param|null, // Default: true
 *     channels?: list<scalar|Param|null>,
 *     handlers?: array<string, array{ // Default: []
 *         type?: scalar|Param|null,
 *         id?: scalar|Param|null,
 *         enabled?: bool|Param, // Default: true
 *         priority?: scalar|Param|null, // Default: 0
 *         level?: scalar|Param|null, // Default: "DEBUG"
 *         bubble?: bool|Param, // Default: true
 *         interactive_only?: bool|Param, // Default: false
 *         app_name?: scalar|Param|null, // Default: null
 *         fill_extra_context?: bool|Param, // Default: false
 *         include_stacktraces?: bool|Param, // Default: false
 *         process_psr_3_messages?: array{
 *             enabled?: bool|Param|null, // Default: null
 *             date_format?: scalar|Param|null,
 *             remove_used_context_fields?: bool|Param,
 *         },
 *         path?: scalar|Param|null, // Default: "%kernel.logs_dir%/%kernel.environment%.log"
 *         file_permission?: scalar|Param|null, // Default: null
 *         use_locking?: bool|Param, // Default: false
 *         filename_format?: scalar|Param|null, // Default: "{filename}-{date}"
 *         date_format?: scalar|Param|null, // Default: "Y-m-d"
 *         ident?: scalar|Param|null, // Default: false
 *         logopts?: scalar|Param|null, // Default: 1
 *         facility?: scalar|Param|null, // Default: "user"
 *         max_files?: scalar|Param|null, // Default: 0
 *         action_level?: scalar|Param|null, // Default: "WARNING"
 *         activation_strategy?: scalar|Param|null, // Default: null
 *         stop_buffering?: bool|Param, // Default: true
 *         passthru_level?: scalar|Param|null, // Default: null
 *         excluded_404s?: list<scalar|Param|null>,
 *         excluded_http_codes?: list<array{ // Default: []
 *             code?: scalar|Param|null,
 *             urls?: list<scalar|Param|null>,
 *         }>,
 *         accepted_levels?: list<scalar|Param|null>,
 *         min_level?: scalar|Param|null, // Default: "DEBUG"
 *         max_level?: scalar|Param|null, // Default: "EMERGENCY"
 *         buffer_size?: scalar|Param|null, // Default: 0
 *         flush_on_overflow?: bool|Param, // Default: false
 *         handler?: scalar|Param|null,
 *         url?: scalar|Param|null,
 *         exchange?: scalar|Param|null,
 *         exchange_name?: scalar|Param|null, // Default: "log"
 *         room?: scalar|Param|null,
 *         message_format?: scalar|Param|null, // Default: "text"
 *         api_version?: scalar|Param|null, // Default: null
 *         channel?: scalar|Param|null, // Default: null
 *         bot_name?: scalar|Param|null, // Default: "Monolog"
 *         use_attachment?: scalar|Param|null, // Default: true
 *         use_short_attachment?: scalar|Param|null, // Default: false
 *         include_extra?: scalar|Param|null, // Default: false
 *         icon_emoji?: scalar|Param|null, // Default: null
 *         webhook_url?: scalar|Param|null,
 *         exclude_fields?: list<scalar|Param|null>,
 *         team?: scalar|Param|null,
 *         notify?: scalar|Param|null, // Default: false
 *         nickname?: scalar|Param|null, // Default: "Monolog"
 *         token?: scalar|Param|null,
 *         region?: scalar|Param|null,
 *         source?: scalar|Param|null,
 *         use_ssl?: bool|Param, // Default: true
 *         user?: mixed,
 *         title?: scalar|Param|null, // Default: null
 *         host?: scalar|Param|null, // Default: null
 *         port?: scalar|Param|null, // Default: 514
 *         config?: list<scalar|Param|null>,
 *         members?: list<scalar|Param|null>,
 *         connection_string?: scalar|Param|null,
 *         timeout?: scalar|Param|null,
 *         time?: scalar|Param|null, // Default: 60
 *         deduplication_level?: scalar|Param|null, // Default: 400
 *         store?: scalar|Param|null, // Default: null
 *         connection_timeout?: scalar|Param|null,
 *         persistent?: bool|Param,
 *         dsn?: scalar|Param|null,
 *         hub_id?: scalar|Param|null, // Default: null
 *         client_id?: scalar|Param|null, // Default: null
 *         auto_log_stacks?: scalar|Param|null, // Default: false
 *         release?: scalar|Param|null, // Default: null
 *         environment?: scalar|Param|null, // Default: null
 *         message_type?: scalar|Param|null, // Default: 0
 *         parse_mode?: scalar|Param|null, // Default: null
 *         disable_webpage_preview?: bool|Param|null, // Default: null
 *         disable_notification?: bool|Param|null, // Default: null
 *         split_long_messages?: bool|Param, // Default: false
 *         delay_between_messages?: bool|Param, // Default: false
 *         topic?: int|Param, // Default: null
 *         factor?: int|Param, // Default: 1
 *         tags?: list<scalar|Param|null>,
 *         console_formater_options?: mixed, // Deprecated: "monolog.handlers..console_formater_options.console_formater_options" is deprecated, use "monolog.handlers..console_formater_options.console_formatter_options" instead.
 *         console_formatter_options?: mixed, // Default: []
 *         formatter?: scalar|Param|null,
 *         nested?: bool|Param, // Default: false
 *         publisher?: string|array{
 *             id?: scalar|Param|null,
 *             hostname?: scalar|Param|null,
 *             port?: scalar|Param|null, // Default: 12201
 *             chunk_size?: scalar|Param|null, // Default: 1420
 *             encoder?: "json"|"compressed_json"|Param,
 *         },
 *         mongo?: string|array{
 *             id?: scalar|Param|null,
 *             host?: scalar|Param|null,
 *             port?: scalar|Param|null, // Default: 27017
 *             user?: scalar|Param|null,
 *             pass?: scalar|Param|null,
 *             database?: scalar|Param|null, // Default: "monolog"
 *             collection?: scalar|Param|null, // Default: "logs"
 *         },
 *         mongodb?: string|array{
 *             id?: scalar|Param|null, // ID of a MongoDB\Client service
 *             uri?: scalar|Param|null,
 *             username?: scalar|Param|null,
 *             password?: scalar|Param|null,
 *             database?: scalar|Param|null, // Default: "monolog"
 *             collection?: scalar|Param|null, // Default: "logs"
 *         },
 *         elasticsearch?: string|array{
 *             id?: scalar|Param|null,
 *             hosts?: list<scalar|Param|null>,
 *             host?: scalar|Param|null,
 *             port?: scalar|Param|null, // Default: 9200
 *             transport?: scalar|Param|null, // Default: "Http"
 *             user?: scalar|Param|null, // Default: null
 *             password?: scalar|Param|null, // Default: null
 *         },
 *         index?: scalar|Param|null, // Default: "monolog"
 *         document_type?: scalar|Param|null, // Default: "logs"
 *         ignore_error?: scalar|Param|null, // Default: false
 *         redis?: string|array{
 *             id?: scalar|Param|null,
 *             host?: scalar|Param|null,
 *             password?: scalar|Param|null, // Default: null
 *             port?: scalar|Param|null, // Default: 6379
 *             database?: scalar|Param|null, // Default: 0
 *             key_name?: scalar|Param|null, // Default: "monolog_redis"
 *         },
 *         predis?: string|array{
 *             id?: scalar|Param|null,
 *             host?: scalar|Param|null,
 *         },
 *         from_email?: scalar|Param|null,
 *         to_email?: list<scalar|Param|null>,
 *         subject?: scalar|Param|null,
 *         content_type?: scalar|Param|null, // Default: null
 *         headers?: list<scalar|Param|null>,
 *         mailer?: scalar|Param|null, // Default: null
 *         email_prototype?: string|array{
 *             id?: scalar|Param|null,
 *             method?: scalar|Param|null, // Default: null
 *         },
 *         lazy?: bool|Param, // Default: true
 *         verbosity_levels?: array{
 *             VERBOSITY_QUIET?: scalar|Param|null, // Default: "ERROR"
 *             VERBOSITY_NORMAL?: scalar|Param|null, // Default: "WARNING"
 *             VERBOSITY_VERBOSE?: scalar|Param|null, // Default: "NOTICE"
 *             VERBOSITY_VERY_VERBOSE?: scalar|Param|null, // Default: "INFO"
 *             VERBOSITY_DEBUG?: scalar|Param|null, // Default: "DEBUG"
 *         },
 *         channels?: string|array{
 *             type?: scalar|Param|null,
 *             elements?: list<scalar|Param|null>,
 *         },
 *     }>,
 * }
 * @psalm-type DebugConfig = array{
 *     max_items?: int|Param, // Max number of displayed items past the first level, -1 means no limit. // Default: 2500
 *     min_depth?: int|Param, // Minimum tree depth to clone all the items, 1 is default. // Default: 1
 *     max_string_length?: int|Param, // Max length of displayed strings, -1 means no limit. // Default: -1
 *     dump_destination?: scalar|Param|null, // A stream URL where dumps should be written to. // Default: null
 *     theme?: "dark"|"light"|Param, // Changes the color of the dump() output when rendered directly on the templating. "dark" (default) or "light". // Default: "dark"
 * }
 * @psalm-type MakerConfig = array{
 *     root_namespace?: scalar|Param|null, // Default: "App"
 *     generate_final_classes?: bool|Param, // Default: true
 *     generate_final_entities?: bool|Param, // Default: false
 * }
 * @psalm-type ConfigType = array{
 *     imports?: ImportsConfig,
 *     parameters?: ParametersConfig,
 *     services?: ServicesConfig,
 *     framework?: FrameworkConfig,
 *     doctrine?: DoctrineConfig,
 *     doctrine_migrations?: DoctrineMigrationsConfig,
 *     security?: SecurityConfig,
 *     twig?: TwigConfig,
 *     web_profiler?: WebProfilerConfig,
 *     monolog?: MonologConfig,
 *     debug?: DebugConfig,
 *     "when@dev"?: array{
 *         imports?: ImportsConfig,
 *         parameters?: ParametersConfig,
 *         services?: ServicesConfig,
 *         framework?: FrameworkConfig,
 *         doctrine?: DoctrineConfig,
 *         doctrine_migrations?: DoctrineMigrationsConfig,
 *         security?: SecurityConfig,
 *         twig?: TwigConfig,
 *         web_profiler?: WebProfilerConfig,
 *         monolog?: MonologConfig,
 *         debug?: DebugConfig,
 *         maker?: MakerConfig,
 *     },
 *     "when@test"?: array{
 *         imports?: ImportsConfig,
 *         parameters?: ParametersConfig,
 *         services?: ServicesConfig,
 *         framework?: FrameworkConfig,
 *         doctrine?: DoctrineConfig,
 *         doctrine_migrations?: DoctrineMigrationsConfig,
 *         security?: SecurityConfig,
 *         twig?: TwigConfig,
 *         web_profiler?: WebProfilerConfig,
 *         monolog?: MonologConfig,
 *         debug?: DebugConfig,
 *     },
 *     ...<string, ExtensionType|array{ // extra keys must follow the when@%env% pattern or match an extension alias
 *         imports?: ImportsConfig,
 *         parameters?: ParametersConfig,
 *         services?: ServicesConfig,
 *         ...<string, ExtensionType>,
 *     }>
 * }
 */
final class App
{
    /**
     * @param ConfigType $config
     *
     * @psalm-return ConfigType
     */
    public static function config(array $config): array
    {
        /** @var ConfigType $config */
        $config = AppReference::config($config);

        return $config;
    }
}

namespace Symfony\Component\Routing\Loader\Configurator;

/**
 * This class provides array-shapes for configuring the routes of an application.
 *
 * Example:
 *
 *     ```php
 *     // config/routes.php
 *     namespace Symfony\Component\Routing\Loader\Configurator;
 *
 *     return Routes::config([
 *         'controllers' => [
 *             'resource' => 'routing.controllers',
 *         ],
 *     ]);
 *     ```
 *
 * @psalm-type RouteConfig = array{
 *     path: string|array<string,string>,
 *     controller?: string,
 *     methods?: string|list<string>,
 *     requirements?: array<string,string>,
 *     defaults?: array<string,mixed>,
 *     options?: array<string,mixed>,
 *     host?: string|array<string,string>,
 *     schemes?: string|list<string>,
 *     condition?: string,
 *     locale?: string,
 *     format?: string,
 *     utf8?: bool,
 *     stateless?: bool,
 * }
 * @psalm-type ImportConfig = array{
 *     resource: string,
 *     type?: string,
 *     exclude?: string|list<string>,
 *     prefix?: string|array<string,string>,
 *     name_prefix?: string,
 *     trailing_slash_on_root?: bool,
 *     controller?: string,
 *     methods?: string|list<string>,
 *     requirements?: array<string,string>,
 *     defaults?: array<string,mixed>,
 *     options?: array<string,mixed>,
 *     host?: string|array<string,string>,
 *     schemes?: string|list<string>,
 *     condition?: string,
 *     locale?: string,
 *     format?: string,
 *     utf8?: bool,
 *     stateless?: bool,
 * }
 * @psalm-type AliasConfig = array{
 *     alias: string,
 *     deprecated?: array{package:string, version:string, message?:string},
 * }
 * @psalm-type RoutesConfig = array{
 *     "when@dev"?: array<string, RouteConfig|ImportConfig|AliasConfig>,
 *     "when@test"?: array<string, RouteConfig|ImportConfig|AliasConfig>,
 *     ...<string, RouteConfig|ImportConfig|AliasConfig>
 * }
 */
final class Routes
{
    /**
     * @param RoutesConfig $config
     *
     * @psalm-return RoutesConfig
     */
    public static function config(array $config): array
    {
        return $config;
    }
}


================================================
FILE: config/routes/attributes.yaml
================================================
controllers:
    resource: ../../src/Controller/
    type: attribute

kernel:
    resource: App\Kernel
    type: attribute


================================================
FILE: config/routes/dev/framework.yaml
================================================
_errors:
    resource: '@FrameworkBundle/Resources/config/routing/errors.php'
    prefix: /_error

================================================
FILE: config/routes/dev/web_profiler.yaml
================================================
web_profiler_wdt:
    resource: '@WebProfilerBundle/Resources/config/routing/wdt.php'
    prefix: /_wdt

web_profiler_profiler:
    resource: '@WebProfilerBundle/Resources/config/routing/profiler.php'
    prefix: /_profiler

================================================
FILE: config/routes.yaml
================================================
#index:
#    path: /
#    controller: App\Controller\DefaultController::index


================================================
FILE: config/services.yaml
================================================
# This file is the entry point to configure your own services.
# Files in the packages/ subdirectory configure your dependencies.

# Put parameters here that don't need to change on each machine where the app is deployed
# https://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration
parameters:
    default_database_driver: "mysql"
    default_admin_auth_bypass: "false"
    timezone: '%env(APP_TIMEZONE)%'
    public_calendars_enabled: '%env(default:default_public_calendars_enabled:bool:PUBLIC_CALENDARS_ENABLED)%'
    default_public_calendars_enabled: "true"
    birthday_reminder_offset: '%env(default:default_birthday_reminder_offset:BIRTHDAY_REMINDER_OFFSET)%'
    default_birthday_reminder_offset: "PT9H"
    caldav_enabled: "%env(bool:CALDAV_ENABLED)%"
    carddav_enabled: "%env(bool:CARDDAV_ENABLED)%"

services:
    # default configuration for services in *this* file
    _defaults:
        autowire: true      # Automatically injects dependencies in your services.
        autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.

    # makes classes in src/ available to be used as services
    # this creates a service per class whose id is the fully-qualified class name
    App\:
        resource: '../src/*'
        exclude: '../src/{DependencyInjection,Entity,Migrations,Tests,Kernel.php}'

    App\Services\Utils:
        arguments:
            $authRealm: "%env(AUTH_REALM)%"

    App\Services\IMAPAuth:
        arguments:
            $IMAPAuthUrl: "%env(IMAP_AUTH_URL)%"
            $IMAPEncryptionMethod: "%env(IMAP_ENCRYPTION_METHOD)%"
            $IMAPCertificateValidation: "%env(bool:IMAP_CERTIFICATE_VALIDATION)%"
            $autoCreate: "%env(bool:IMAP_AUTH_USER_AUTOCREATE)%"

    App\Services\LDAPAuth:
        arguments:
            $LDAPAuthUrl: "%env(LDAP_AUTH_URL)%"
            $LDAPDnPattern: "%env(LDAP_DN_PATTERN)%"
            $LDAPMailAttribute: "%env(LDAP_MAIL_ATTRIBUTE)%"
            $LDAPCertificateCheckingStrategy: "%env(LDAP_CERTIFICATE_CHECKING_STRATEGY)%"
            $autoCreate: "%env(bool:LDAP_AUTH_USER_AUTOCREATE)%"

    # controllers are imported separately to make sure services can be injected
    # as action arguments even if you don't extend any base controller class
    App\Controller\:
        resource: '../src/Controller'
        tags: ['controller.service_arguments']

    App\Controller\DAVController:
        arguments:
            $publicDir: "%kernel.project_dir%/public"
            $calDAVEnabled: "%env(bool:CALDAV_ENABLED)%"
            $cardDAVEnabled: "%env(bool:CARDDAV_ENABLED)%"
            $webDAVEnabled: "%env(bool:WEBDAV_ENABLED)%"
            $publicCalendarsEnabled: "%public_calendars_enabled%"
            $inviteAddress: "%env(INVITE_FROM_ADDRESS)%"
            $authMethod: "%env(AUTH_METHOD)%"
            $authRealm: "%env(AUTH_REALM)%"
            $webdavPublicDir: "%env(resolve:WEBDAV_PUBLIC_DIR)%"
            $webdavHomesDir: "%env(resolve:WEBDAV_HOMES_DIR)%"
            $webdavTmpDir: "%env(resolve:WEBDAV_TMP_DIR)%"

    App\Security\LoginFormAuthenticator:
        arguments:
            $adminLogin: "%env(ADMIN_LOGIN)%"
            $adminPassword: "%env(ADMIN_PASSWORD)%"

    App\Logging\Monolog\PasswordFilterProcessor:
        tags:
            - { name: monolog.processor }

    App\Services\BirthdayService:
        arguments:
            $birthdayReminderOffset: "%birthday_reminder_offset%"

    App\Security\ApiKeyAuthenticator:
        arguments:
            $apiKey: "%env(API_KEY)%"

when@dev:
    services:
        Symfony\Component\HttpKernel\Profiler\Profiler: '@profiler'

when@test:
    services:
        Symfony\Component\HttpKernel\Profiler\Profiler: '@profiler'


================================================
FILE: docker/Dockerfile
================================================
# syntax=docker/dockerfile:1

# Initial PHP image is available here:
# https://github.com/docker-library/php/blob/master/8.2/alpine3.18/fpm/Dockerfile#L33
ARG fpm_user=82:82

# Base image, used to build extensions and the final image ———————————————————————
FROM php:8.3-fpm-alpine AS base-image

# Run update, and gets basic packages and packages for runtime
RUN apk --no-progress --update add --no-cache \
        curl unzip fcgi \
        # These are for php-intl
        icu-libs \
        # This one for LDAP
        libldap \
        # This one for IMAP
        libzip \
        # These are for GD (map image in mail)
        freetype \
        libjpeg-turbo \
        libpng \
        # This is for PostgreSQL
        libpq

# Build all extensions in a separate image ———————————————————————————————————————
FROM base-image AS extension-builder

# Install ALL build dependencies at once
# hadolint ignore=SC2086
RUN apk --update --virtual build-deps add --no-cache \
    # Intl support
    icu-dev \
    # PDO: PostgreSQL
    libpq-dev \
    # GD (map image in mail)
    freetype-dev libjpeg-turbo-dev libpng-dev \
    # LDAP auth support
    openldap-dev \
    # Zip lib for PHP-IMAP
    libzip-dev \
    $PHPIZE_DEPS

# Build ALL extensions in one RUN command to reduce layers
# and allow parallel compilation for extensions
RUN docker-php-ext-configure intl \
    && docker-php-ext-configure pdo_mysql --with-pdo-mysql=mysqlnd \
    && docker-php-ext-configure pgsql -with-pgsql=/usr/local/pgsql \
    && docker-php-ext-configure gd --with-freetype \
    && docker-php-ext-configure ldap \
    && docker-php-ext-configure zip \
    && docker-php-ext-install -j"$(nproc)" \
        intl \
        pdo_mysql \
        pgsql \
        pdo_pgsql \
        gd \
        ldap \
        zip \
        opcache \
    && docker-php-ext-enable gd opcache \
    && apk del build-deps \
    && rm -rf /tmp/* /var/cache/apk/*

COPY ./docker/configurations/opcache.ini /usr/local/etc/php/conf.d/opcache.ini

# Final image ————————————————————————————————————————————————————————————————————
FROM base-image

ARG fpm_user=82:82
ENV FPM_USER=${fpm_user}

ENV PHP_OPCACHE_MEMORY_CONSUMPTION="256" \
    PHP_OPCACHE_MAX_WASTED_PERCENTAGE="10"

LABEL org.opencontainers.image.authors="tchap@tchap.me"
LABEL org.opencontainers.image.url="https://github.com/tchapi/davis/pkgs/container/davis"
LABEL org.opencontainers.image.description="A simple, fully translatable admin interface for sabre/dav based on Symfony 7 and Bootstrap 5"

# Rapatriate built extensions
COPY --from=extension-builder /usr/local/etc/php/conf.d     /usr/local/etc/php/conf.d/
COPY --from=extension-builder /usr/local/lib/php/extensions /usr/local/lib/php/extensions/

# This is for Hadolint
SHELL ["/bin/ash", "-eo", "pipefail", "-c"]

# PHP-FPM healthcheck
RUN set -xe && echo "pm.status_path = /status" >> /usr/local/etc/php-fpm.d/zz-docker.conf
RUN curl https://raw.githubusercontent.com/renatomefi/php-fpm-healthcheck/v0.5.0/php-fpm-healthcheck \
    -o /usr/local/bin/php-fpm-healthcheck -s \
    && chmod +x /usr/local/bin/php-fpm-healthcheck

# Davis installation
COPY --chown=${FPM_USER} . /var/www/davis
WORKDIR /var/www/davis

# Install Composer 2, then dependencies, compress the rather big INTL package, and then cleanup (only useful when using --squash)
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
RUN APP_ENV=prod COMPOSER_ALLOW_SUPERUSER=1 composer install --no-ansi --no-dev --no-interaction --no-progress --prefer-dist --optimize-autoloader \
    && php ./vendor/symfony/intl/Resources/bin/compress \
    && rm -rf /var/www/davis/docker

USER $FPM_USER

HEALTHCHECK --interval=30s --timeout=1s CMD php-fpm-healthcheck || exit 1


================================================
FILE: docker/Dockerfile-standalone
================================================
# Initial PHP image is available here:
# https://github.com/docker-library/php/blob/master/8.2/alpine3.18/fpm/Dockerfile#L33

# Base image, used to build extensions and the final image ———————————————————————
FROM php:8.3-fpm-alpine AS base-image

# Run update, and gets basic packages and packages for runtime
RUN apk --no-progress --update add --no-cache \
        curl unzip \
        # These are for php-intl
        icu-libs \
        # This one for LDAP
        libldap \
        # This one for IMAP
        libzip \
        # These are for GD (map image in mail)
        freetype \
        libjpeg-turbo \
        libpng \
        # This is for PostgreSQL
        libpq \
        # For the webserver and process manager
        caddy supervisor

# Build all extensions in a separate image ———————————————————————————————————————
FROM base-image AS extension-builder

# Install ALL build dependencies at once
# hadolint ignore=SC2086
RUN apk --update --virtual build-deps add --no-cache \
    # Intl support
    icu-dev \
    # PDO: PostgreSQL
    libpq-dev \
    # GD (map image in mail)
    freetype-dev libjpeg-turbo-dev libpng-dev \
    # LDAP auth support
    openldap-dev \
    # Zip lib for PHP-IMAP
    libzip-dev \
    $PHPIZE_DEPS

# Build ALL extensions in one RUN command to reduce layers
# and allow parallel compilation for extensions
RUN docker-php-ext-configure intl \
    && docker-php-ext-configure pdo_mysql --with-pdo-mysql=mysqlnd \
    && docker-php-ext-configure pgsql -with-pgsql=/usr/local/pgsql \
    && docker-php-ext-configure gd --with-freetype \
    && docker-php-ext-configure ldap \
    && docker-php-ext-configure zip \
    && docker-php-ext-install -j"$(nproc)" \
        intl \
        pdo_mysql \
        pgsql \
        pdo_pgsql \
        gd \
        ldap \
        zip \
        opcache \
    && docker-php-ext-enable gd opcache \
    && apk del build-deps \
    && rm -rf /tmp/* /var/cache/apk/*

COPY ./docker/configurations/opcache.ini /usr/local/etc/php/conf.d/opcache.ini

# Final image ————————————————————————————————————————————————————————————————————
FROM base-image

ENV PHP_OPCACHE_MEMORY_CONSUMPTION="256" \
    PHP_OPCACHE_MAX_WASTED_PERCENTAGE="10"

LABEL org.opencontainers.image.authors="tchap@tchap.me"
LABEL org.opencontainers.image.url="https://github.com/tchapi/davis/pkgs/container/davis-standalone"
LABEL org.opencontainers.image.description="A simple, fully translatable admin interface for sabre/dav based on Symfony 7 and Bootstrap 5 (Standalone version with reverse-proxy)"

# Rapatriate built extensions
COPY --from=extension-builder /usr/local/etc/php/conf.d     /usr/local/etc/php/conf.d/
COPY --from=extension-builder /usr/local/lib/php/extensions /usr/local/lib/php/extensions/

# Davis source
# The app folder needs to be owned by www-data so PHP-fpm can execute files
COPY --chown=www-data:www-data . /var/www/davis
WORKDIR /var/www/davis

# This is for Hadolint
SHELL ["/bin/ash", "-eo", "pipefail", "-c"]

# Install Composer 2, then dependencies, compress the rather big INTL package
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
RUN APP_ENV=prod COMPOSER_ALLOW_SUPERUSER=1 composer install --no-ansi --no-dev --no-interaction --no-progress --prefer-dist --optimize-autoloader \
    && php ./vendor/symfony/intl/Resources/bin/compress

# Caddy: web server
RUN mkdir -p /var/log/caddy
COPY ./docker/configurations/Caddyfile /etc/caddy/Caddyfile

# Supervisor: Process manager
RUN mkdir -p /var/log/supervisor && mkdir -p /var/log/php-fpm
COPY ./docker/configurations/supervisord.conf /etc/supervisord.conf

# We want to use sockets inside the container between Caddy and PHP-fpm
# NOTE: Creating a custom zzzz-custom.conf overrides the www.conf setting (files are processed alphabetically)
RUN mkdir /var/run/php-fpm && chown -R www-data:www-data /var/run/php-fpm \
    && { \
        echo '[www]'; \
        echo 'listen = /var/run/php-fpm/php-fpm.sock'; \
    } | tee /usr/local/etc/php-fpm.d/zzzz-custom-docker.conf

RUN mkdir -p ./var/log ./var/cache && chown -R www-data:www-data ./var

# Cleanup (only useful when using --squash)
RUN docker-php-source delete && \
    rm -rf /var/www/davis/docker

CMD ["/usr/bin/supervisord", "-c", "/etc/supervisord.conf"]

HEALTHCHECK --interval=120s --timeout=10s --start-period=60s --retries=3 \
    CMD curl --fail http://localhost:9000 || exit 1 

# It's the Caddy port, not the PHP-fpm one (as we use sockets)
EXPOSE 9000

================================================
FILE: docker/configurations/Caddyfile
================================================
{
        auto_https off
}

:9000 {
	# Redirect .well-known
	redir /.well-known/caldav  /dav/
	redir /.well-known/carddav /dav/

        root * /var/www/davis/public
        php_fastcgi unix//var/run/php-fpm/php-fpm.sock {
                # Preserve the original X-Forwarded-Proto from upstream, as it might be HTTPS
                header_up X-Forwarded-Proto {http.request.header.X-Forwarded-Proto}
                header_up X-Forwarded-Host {http.request.header.X-Forwarded-Host}
                header_up X-Forwarded-For {http.request.header.X-Forwarded-For}
        }

        file_server {
                # Safety net, just in case
                hide .git .gitignore
        }

        # enable compression
        encode zstd gzip

        # Remove leaky headers
        header {
                -Server
                -X-Powered-By

                # keep referrer data off of HTTP connections
                Referrer-Policy no-referrer-when-downgrade

                # disable clients from sniffing the media type
                X-Content-Type-Options nosniff
        }

}


================================================
FILE: docker/configurations/nginx.conf
================================================
# This is a very simple / naive configuration for nginx + Davis
#
# USE HTTPS IN PRODUCTION
#

upstream docker-davis {
    server davis:9000;
}

server {
    listen 80;
    access_log off;

    root /var/www/davis/public/;
    index index.php;

    rewrite ^/.well-known/caldav /dav/ redirect;
    rewrite ^/.well-known/carddav /dav/ redirect;

    charset utf-8;

    location ~ /(\.ht) {
        deny all;
        return 404;
    }

    location / {
        try_files $uri $uri/ /index.php$is_args$args;
    }

    location ~ ^(.+\.php)(.*)$ {
        try_files       $fastcgi_script_name =404;
        include         fastcgi_params;
        fastcgi_pass    docker-davis;
        fastcgi_param   SCRIPT_FILENAME  $document_root$fastcgi_script_name;
        fastcgi_param   PATH_INFO        $fastcgi_path_info;
        fastcgi_split_path_info  ^(.+\.php)(.*)$;
    }
}


================================================
FILE: docker/configurations/opcache.ini
================================================
opcache.enable=1
opcache.jit=1255
opcache.jit_buffer_size=128M
opcache.revalidate_freq=60
opcache.save_comments=1
opcache.validate_timestamps=1
opcache.max_accelerated_files=32531
opcache.memory_consumption=${PHP_OPCACHE_MEMORY_CONSUMPTION}
opcache.max_wasted_percentage=${PHP_OPCACHE_MAX_WASTED_PERCENTAGE}
opcache.interned_strings_buffer=64

================================================
FILE: docker/configurations/supervisord.conf
================================================
[supervisord]
nodaemon=true
user=root
pidfile=/run/supervisord.pid
logfile=/dev/null
logfile_maxbytes=0

[unix_http_server]
file=/run/supervisord.sock ; the path to the socket file

[supervisorctl]
serverurl=unix:///run/supervisord.sock ; use a unix:// URL for a unix socket

[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface

[program:caddy]
command=/usr/sbin/caddy run -c /etc/caddy/Caddyfile
autostart=true
autorestart=true
redirect_stderr=true
stdout_logfile=/var/log/caddy/access.log
stdout_logfile_maxbytes = 0

[program:php-fpm]
command=/usr/local/sbin/php-fpm --nodaemonize
autostart=true
autorestart=true
redirect_stderr=true
stdout_logfile=/var/log/php-fpm/access.log
stdout_logfile_maxbytes = 0

================================================
FILE: docker/docker-compose-postgresql.yml
================================================
version: "3.7"
name: "davis-docker"

services:

  nginx:
    image: nginx:1.25-alpine
    container_name: nginx
    command: "/bin/sh -c 'while :; do sleep 6h & wait $${!}; nginx -s reload; done & nginx -g \"daemon off;\"'"
    depends_on:
      - davis
    volumes:
      - davis_www:/var/www/davis
      - type: bind
        source: ./configurations/nginx.conf
        target: /etc/nginx/conf.d/default.conf
    ports:
      - 9000:80

  postgresql:
    image: postgres:16-alpine
    container_name: postgresql
    environment:
      - POSTGRES_PASSWORD=${DB_PASSWORD}
      - POSTGRES_DB=${DB_DATABASE}
      - POSTGRES_USER=${DB_USER}
    volumes:
      - database_pg:/var/lib/postgresql/data

  davis:
    build:
      context: ../
      dockerfile: ./docker/Dockerfile
    image: davis:latest
    # If you want to use a prebuilt image from Github
    # image: ghcr.io/tchapi/davis:edge
    container_name: davis
    env_file: .env
    environment:
      - DATABASE_DRIVER=postgresql
      - DATABASE_URL=postgresql://${DB_USER}:${DB_PASSWORD}@postgresql:5432/${DB_DATABASE}?serverVersion=15&charset=UTF-8
      - MAILER_DSN=smtp://${MAIL_USERNAME}:${MAIL_PASSWORD}@${MAIL_HOST}:${MAIL_PORT}
    depends_on:
      - postgresql
    volumes:
      - davis_www:/var/www/davis

volumes:
  davis_www:
    name: davis_www
  database_pg:
    name: database_pg


================================================
FILE: docker/docker-compose-sqlite.yml
================================================
version: "3.7"
name: "davis-docker"

services:

  nginx:
    image: nginx:1.25-alpine
    container_name: nginx
    command: "/bin/sh -c 'while :; do sleep 6h & wait $${!}; nginx -s reload; done & nginx -g \"daemon off;\"'"
    depends_on:
      - davis
    volumes:
      - davis_www:/var/www/davis
      - type: bind
        source: ./configurations/nginx.conf
        target: /etc/nginx/conf.d/default.conf
    ports:
      - 9000:80

  davis:
    build:
      context: ../
      dockerfile: ./docker/Dockerfile
    image: davis:latest
    # If you want to use a prebuilt image from Github
    # image: ghcr.io/tchapi/davis:edge
    container_name: davis
    env_file: .env
    environment:
      - DATABASE_DRIVER=sqlite
      - DATABASE_URL=sqlite:////data/davis-database.db # ⚠️ 4 slashes for an absolute path ⚠️ + no quotes (so Symfony can resolve it)
      - MAILER_DSN=smtp://${MAIL_USERNAME}:${MAIL_PASSWORD}@${MAIL_HOST}:${MAIL_PORT}
    volumes:
      - davis_www:/var/www/davis
      - davis_data:/data

volumes:
  davis_www:
    name: davis_www
  davis_data:
    name: davis_data


================================================
FILE: docker/docker-compose-standalone.yml
================================================
version: "3.7"
name: "davis-docker"

services:

  mysql:
    image: mariadb:10.6.10
    container_name: mysql
    environment:
      - MYSQL_ROOT_PASSWORD=${DB_ROOT_PASSWORD}
      - MYSQL_DATABASE=${DB_DATABASE}
      - MYSQL_USER=${DB_USER}
      - MYSQL_PASSWORD=${DB_PASSWORD}
    volumes:
      - database:/var/lib/mysql

  davis:
    build:
      context: ../
      dockerfile: ./docker/Dockerfile-standalone
    image: davis:latest
    # If you want to use a prebuilt image from Github
    # image: ghcr.io/tchapi/davis-standalone:edge
    container_name: davis-standalone
    env_file: .env
    environment:
      - DATABASE_DRIVER=mysql
      - DATABASE_URL=mysql://${DB_USER}:${DB_PASSWORD}@mysql:3306/${DB_DATABASE}?serverVersion=mariadb-10.6.10&charset=utf8mb4
      - MAILER_DSN=smtp://${MAIL_USERNAME}:${MAIL_PASSWORD}@${MAIL_HOST}:${MAIL_PORT}
    depends_on:
      - mysql
    ports:
      - 9000:9000

volumes:
  database:
    name: database


================================================
FILE: docker/docker-compose.yml
================================================
version: "3.7"
name: "davis-docker"

services:

  nginx:
    image: nginx:1.25-alpine
    container_name: nginx
    command: "/bin/sh -c 'while :; do sleep 6h & wait $${!}; nginx -s reload; done & nginx -g \"daemon off;\"'"
    depends_on:
      - davis
    volumes:
      - davis_www:/var/www/davis
      - type: bind
        source: ./configurations/nginx.conf
        target: /etc/nginx/conf.d/default.conf
    ports:
      - 9000:80

  mysql:
    image: mariadb:10.11
    container_name: mysql
    environment:
      - MYSQL_ROOT_PASSWORD=${DB_ROOT_PASSWORD}
      - MYSQL_DATABASE=${DB_DATABASE}
      - MYSQL_USER=${DB_USER}
      - MYSQL_PASSWORD=${DB_PASSWORD}
    volumes:
      - database:/var/lib/mysql

  davis:
    build:
      context: ../
      dockerfile: ./docker/Dockerfile
      args:
        fpm_user: 101:101
    image: davis:latest
    # If you want to use a prebuilt image from Github
    # image: ghcr.io/tchapi/davis:edge
    container_name: davis
    env_file: .env
    environment:
      - DATABASE_DRIVER=mysql
      - DATABASE_URL=mysql://${DB_USER}:${DB_PASSWORD}@mysql:3306/${DB_DATABASE}?serverVersion=mariadb-10.6.10&charset=utf8mb4
      - MAILER_DSN=smtp://${MAIL_USERNAME}:${MAIL_PASSWORD}@${MAIL_HOST}:${MAIL_PORT}
    depends_on:
      - mysql
    volumes:
      - davis_www:/var/www/davis

volumes:
  davis_www:
    name: davis_www
  database:
    name: database


================================================
FILE: docs/api/README.md
================================================
# Davis API

## API Version 1

### Open Endpoints

Open endpoints require no Authentication.

* [Health](v1/health.md) : `GET /api/v1/health`

### Endpoints that require Authentication

Closed endpoints require a valid `X-Davis-API-Token` to be included in the header of the request. Token needs to be configured in .env file (as a environment variable `API_KEY`) and can be generated using `php bin/console api:generate` command.

When `API_KEY` is not set, the API endpoints are disabled and will return a 500 error if accessed.

#### User related

Each endpoint displays information related to the User:

* [Get Users](v1/users/all.md) : `GET /api/v1/users`
* [Get User Details](v1/users/details.md) : `GET /api/v1/users/:user_id`

#### Calendars related

Endpoints for viewing and modifying user calendars.

* [Show All User Calendars](v1/calendars/all.md) : `GET /api/v1/calendars/:user_id`
* [Show User Calendar Details](v1/calendars/details.md) : `GET /api/v1/calendars/:user_id/:calendar_id`
* [Create User Calendar](v1/calendars/create.md) : `POST /api/v1/calendars/:user_id/create`
* [Edit User Calendar](v1/calendars/edit.md) : `PUT /api/v1/calendars/:user_id/:calendar_id`
* [Delete User Calendar](v1/calendars/delete.md) : `DELETE /api/v1/calendars/:user_id/:calendar_id`
* [Show User Calendar Shares](v1/calendars/shares.md) : `GET /api/v1/calendars/:user_id/shares/:calendar_id`
* [Share User Calendar](v1/calendars/share_add.md) : `POST /api/v1/calendars/:user_id/share/:calendar_id/add`
* [Remove Share User Calendar](v1/calendars/share_remove.md) : `POST /api/v1/calendars/:user_id/share/:calendar_id/remove`


================================================
FILE: docs/api/v1/calendars/all.md
================================================
# User Calendars

Gets a list of all available calendars for a specific user.

**URL** : `/api/v1/calendars/:user_id`

**Method** : `GET`

**Auth required** : YES

**Params constraints**

```
:user_id -> "[user id as an int]",
```

**URL example**

```json
/api/v1/calendars/jdoe
```

## Success Response

**Code** : `200 OK`

**Notes**: The `events`, `notes`, and `tasks` fields return a count (number) if the component is enabled for the calendar, or `null` if the component is disabled.

**Content examples**

```json
{
	"status": "success",
	"data": {
		"user_calendars": [
			{
				"id": 1,
				"uri": "default",
				"displayname": "Default Calendar",
				"events": 0,
				"notes": null,
				"tasks": null
			}
		],
		"shared_calendars": [
			{
				"id": 10,
				"uri": "c2152eb0-ada1-451f-bf33-b4a9571ec92e",
				"displayname": "Default Calendar",
				"events": 0,
				"notes": null,
				"tasks": null
			}
		],
		"subscriptions": []
	},
	"timestamp": "2026-01-23T15:01:33+01:00"
}
```

Shown when user does not have calendars:
```json
{
	"status": "success",
	"data": {
		"user_calendars": [],
		"shared_calendars": [],
		"subscriptions": []
	},
	"timestamp": "2026-01-23T15:01:33+01:00"
}
```

## Error Response

**Condition** : If 'X-Davis-API-Token' is not present or mismatched in headers.

**Code** : `401 UNAUTHORIZED`

**Content** :

```json
{
	"message": "No API token provided",
	"timestamp": "2026-01-23T15:01:33+01:00"
}
```

or

```json
{
	"message": "Invalid API token",
	"timestamp": "2026-01-23T15:01:33+01:00"
}
```

**Condition** : If user is not found.

**Code** : `404 NOT FOUND`

**Content** :

```json
{
	"status": "error", 
    "message": "User Not Found",
	"timestamp": "2026-01-23T15:01:33+01:00"
}
```

================================================
FILE: docs/api/v1/calendars/create.md
================================================
# Create User Calendar

Creates a new calendar for a specific user.

**URL** : `/api/v1/calendars/:user_id/create`

**Method** : `POST`

**Auth required** : YES

**Params constraints**

```
:user_id -> "[user id as an int]",
```

**Request Body constraints**

```json
{
	"name": "[string: calendar name, alphanumeric, spaces, underscores and hyphens, max 64 chars]",
	"uri": "[string: calendar URI, lowercase alphanumeric, underscores and hyphens, max 128 chars]",
	"description": "[string: calendar description, alphanumeric, spaces, underscores and hyphens, max 256 chars, optional]",
	"events_support": "[string: 'true' or 'false', default 'true', optional]",
	"notes_support": "[string: 'true' or 'false', default 'false', optional]",
	"tasks_support": "[string: 'true' or 'false', default 'false', optional]"
}
```

**URL example**

```
/api/v1/calendars/jdoe/create
```

**Body example**

```json
{
	"name": "Work Calendar",
	"uri": "work-calendar",
	"description": "Calendar for work events",
	"events_support": "true",
	"notes_support": "false",
	"tasks_support": "true"
}
```

## Success Response

**Code** : `200 OK`

**Content examples**

```json
{
	"status": "success",
	"data": {
		"calendar_id": 5,
		"calendar_uri": "work-calendar"
	},
	"timestamp": "2026-01-23T15:01:33+01:00"
}
```

## Error Response

**Condition** : If 'X-Davis-API-Token' is not present or mismatched in headers.

**Code** : `401 UNAUTHORIZED`

**Content** :

```json
{
	"message": "No API token provided",
	"timestamp": "2026-01-23T15:01:33+01:00"
}
```

or

```json
{
	"message": "Invalid API token",
	"timestamp": "2026-01-23T15:01:33+01:00"
}
```

**Condition** : If user is not found.

**Code** : `404 NOT FOUND`

**Content** :

```json
{
	"status": "error",
	"message": "User Not Found",
	"timestamp": "2026-01-23T15:01:33+01:00"
}
```

**Condition** : If request body contains invalid JSON.

**Code** : `400 BAD REQUEST`

**Content** :

```json
{
	"status": "error",
	"message": "Invalid JSON",
	"timestamp": "2026-01-23T15:01:33+01:00"
}
```

**Condition** : If 'name' parameter is invalid (not matching the regex or exceeds length).

**Code** : `400 BAD REQUEST`

**Content** :

```json
{
	"status": "error",
	"message": "Invalid Calendar Name",
	"timestamp": "2026-01-23T15:01:33+01:00"
}
```

**Condition** : If 'uri' parameter is invalid (not matching the regex or exceeds length).

**Code** : `400 BAD REQUEST`

**Content** :

```json
{
	"status": "error",
	"message": "Invalid Calendar URI",
	"timestamp": "2026-01-23T15:01:33+01:00"
}
```

**Condition** : If calendar with specified URI already exists for the user.

**Code** : `400 BAD REQUEST`

**Content** :

```json
{
	"status": "error",
	"message": "Calendar URI Already Exists",
	"timestamp": "2026-01-23T15:01:33+01:00"
}
```

**Condition** : If 'description' parameter is invalid (not matching the regex or exceeds length).

**Code** : `400 BAD REQUEST`

**Content** :

```json
{
	"status": "error",
	"message": "Invalid Calendar Description",
	"timestamp": "2026-01-23T15:01:33+01:00"
}
```

**Condition** : If no calendar components are enabled (all of `events_support`, `notes_support`, and `tasks_support` are false).

**Code** : `400 BAD REQUEST`

**Content** :

```json
{
	"status": "error",
	"message": "At least one calendar component must be enabled (events, notes, or tasks)",
	"timestamp": "2026-01-23T15:01:33+01:00"
}
```


================================================
FILE: docs/api/v1/calendars/delete.md
================================================
# Delete User Calendar

Deletes a specific calendar for a specific user.

**URL** : `/api/v1/calendars/:user_id/:calendar_id`

**Method** : `DELETE`

**Auth required** : YES

**Params constraints**

```
:user_id -> "[user id as an int]",
:calendar_id -> "[numeric id of a calendar owned by the user]",
```

**URL example**

```
/api/v1/calendars/jdoe/1
```

## Success Response

**Code** : `200 OK`

**Content examples**

```json
{
	"status": "success",
	"timestamp": "2026-01-23T15:01:33+01:00"
}
```

## Error Response

**Condition** : If 'X-Davis-API-Token' is not present or mismatched in headers.

**Code** : `401 UNAUTHORIZED`

**Content** :

```json
{
	"message": "No API token provided",
	"timestamp": "2026-01-23T15:01:33+01:00"
}
```

or

```json
{
	"message": "Invalid API token",
	"timestamp": "2026-01-23T15:01:33+01:00"
}
```

**Condition** : If user is not found.

**Code** : `404 NOT FOUND`

**Content** :

```json
{
	"status": "error",
	"message": "User Not Found",
	"timestamp": "2026-01-23T15:01:33+01:00"
}
```

**Condition** : If ':calendar_id' is not owned by the specified ':username' or calendar instance is not found.

**Code** : `400 BAD REQUEST`

**Content** :

```jso
Download .txt
gitextract_80g_njsc/

├── .dockerignore
├── .github/
│   ├── FUNDING.yml
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.yml
│   │   └── feature_request.yml
│   └── workflows/
│       ├── ci.yml
│       └── main.yml
├── .gitignore
├── .hadolint.yaml
├── .php-cs-fixer.php
├── LICENSE
├── README.md
├── bin/
│   ├── console
│   └── phpunit
├── composer.json
├── config/
│   ├── bundles.php
│   ├── packages/
│   │   ├── cache.yaml
│   │   ├── dev/
│   │   │   ├── debug.yaml
│   │   │   ├── monolog.yaml
│   │   │   └── web_profiler.yaml
│   │   ├── doctrine.yaml
│   │   ├── doctrine_migrations.yaml
│   │   ├── framework.yaml
│   │   ├── mailer.yaml
│   │   ├── prod/
│   │   │   ├── deprecations.yaml
│   │   │   ├── doctrine.yaml
│   │   │   ├── monolog.yaml
│   │   │   └── routing.yaml
│   │   ├── routing.yaml
│   │   ├── security.yaml
│   │   ├── test/
│   │   │   ├── framework.yaml
│   │   │   ├── monolog.yaml
│   │   │   ├── twig.yaml
│   │   │   ├── validator.yaml
│   │   │   └── web_profiler.yaml
│   │   ├── translation.yaml
│   │   ├── twig.yaml
│   │   └── validator.yaml
│   ├── reference.php
│   ├── routes/
│   │   ├── attributes.yaml
│   │   └── dev/
│   │       ├── framework.yaml
│   │       └── web_profiler.yaml
│   ├── routes.yaml
│   └── services.yaml
├── docker/
│   ├── Dockerfile
│   ├── Dockerfile-standalone
│   ├── configurations/
│   │   ├── Caddyfile
│   │   ├── nginx.conf
│   │   ├── opcache.ini
│   │   └── supervisord.conf
│   ├── docker-compose-postgresql.yml
│   ├── docker-compose-sqlite.yml
│   ├── docker-compose-standalone.yml
│   └── docker-compose.yml
├── docs/
│   └── api/
│       ├── README.md
│       └── v1/
│           ├── calendars/
│           │   ├── all.md
│           │   ├── create.md
│           │   ├── delete.md
│           │   ├── details.md
│           │   ├── edit.md
│           │   ├── share_add.md
│           │   ├── share_remove.md
│           │   └── shares.md
│           ├── health.md
│           └── users/
│               ├── all.md
│               └── details.md
├── migrations/
│   ├── Version20191030113307.php
│   ├── Version20191113170650.php
│   ├── Version20191125093508.php
│   ├── Version20191202091507.php
│   ├── Version20191203111729.php
│   ├── Version20210928132307.php
│   ├── Version20221106220411.php
│   ├── Version20221106220412.php
│   ├── Version20221211154443.php
│   ├── Version20230209142217.php
│   ├── Version20231001214111.php
│   ├── Version20231001214112.php
│   ├── Version20231001214113.php
│   ├── Version20231229203515.php
│   ├── Version20250409193948.php
│   ├── Version20250421163214.php
│   └── Version20260131161930.php
├── phpunit.xml.dist
├── public/
│   ├── .htaccess
│   ├── css/
│   │   └── style.css
│   ├── index.php
│   ├── js/
│   │   ├── app.js
│   │   └── color.mode.toggler.js
│   ├── robots.txt
│   └── site.webmanifest
├── src/
│   ├── Command/
│   │   ├── ApiGenerateCommand.php
│   │   └── SyncBirthdayCalendars.php
│   ├── Constants.php
│   ├── Controller/
│   │   ├── Admin/
│   │   │   ├── AddressBookController.php
│   │   │   ├── CalendarController.php
│   │   │   ├── DashboardController.php
│   │   │   └── UserController.php
│   │   ├── Api/
│   │   │   └── ApiController.php
│   │   ├── DAVController.php
│   │   └── SecurityController.php
│   ├── DataFixtures/
│   │   └── AppFixtures.php
│   ├── Entity/
│   │   ├── AddressBook.php
│   │   ├── AddressBookChange.php
│   │   ├── Calendar.php
│   │   ├── CalendarChange.php
│   │   ├── CalendarInstance.php
│   │   ├── CalendarObject.php
│   │   ├── CalendarSubscription.php
│   │   ├── Card.php
│   │   ├── Lock.php
│   │   ├── Principal.php
│   │   ├── PropertyStorage.php
│   │   ├── SchedulingObject.php
│   │   └── User.php
│   ├── Form/
│   │   ├── AddressBookType.php
│   │   ├── CalendarInstanceType.php
│   │   └── UserType.php
│   ├── Kernel.php
│   ├── Logging/
│   │   └── Monolog/
│   │       └── PasswordFilterProcessor.php
│   ├── Plugins/
│   │   ├── BirthdayCalendarPlugin.php
│   │   ├── DavisIMipPlugin.php
│   │   └── PublicAwareDAVACLPlugin.php
│   ├── Repository/
│   │   ├── CalendarInstanceRepository.php
│   │   └── PrincipalRepository.php
│   ├── Security/
│   │   ├── AdminUser.php
│   │   ├── AdminUserProvider.php
│   │   ├── ApiKeyAuthenticator.php
│   │   └── LoginFormAuthenticator.php
│   ├── Services/
│   │   ├── BasicAuth.php
│   │   ├── BirthdayService.php
│   │   ├── IMAPAuth.php
│   │   ├── LDAPAuth.php
│   │   └── Utils.php
│   └── Version.php
├── templates/
│   ├── _partials/
│   │   ├── add_delegate_modal.html.twig
│   │   ├── back_button.html.twig
│   │   ├── delegate_row.html.twig
│   │   ├── delete_modal.html.twig
│   │   ├── flashes.html.twig
│   │   ├── navigation.html.twig
│   │   └── share_modal.html.twig
│   ├── addressbooks/
│   │   ├── edit.html.twig
│   │   └── index.html.twig
│   ├── base.html.twig
│   ├── calendars/
│   │   ├── edit.html.twig
│   │   └── index.html.twig
│   ├── dashboard.html.twig
│   ├── index.html.twig
│   ├── mails/
│   │   ├── scheduling.html.twig
│   │   └── scheduling.txt.twig
│   ├── security/
│   │   └── login.html.twig
│   └── users/
│       ├── delegates.html.twig
│       ├── edit.html.twig
│       └── index.html.twig
├── tests/
│   ├── .gitignore
│   ├── Functional/
│   │   ├── Commands/
│   │   │   └── SyncBirthdayCalendarTest.php
│   │   ├── Controllers/
│   │   │   ├── AddressBookControllerTest.php
│   │   │   ├── ApiControllerTest.php
│   │   │   ├── CalendarControllerTest.php
│   │   │   ├── DashboardTest.php
│   │   │   └── UserControllerTest.php
│   │   ├── DavTest.php
│   │   └── Service/
│   │       └── BirthdayServiceTest.php
│   └── bootstrap.php
└── translations/
    ├── .gitignore
    ├── messages+intl-icu.de.xlf
    ├── messages+intl-icu.en.xlf
    ├── messages+intl-icu.fr.xliff
    ├── security.de.xlf
    ├── security.en.xlf
    ├── security.fr.xlf
    ├── validators.de.xlf
    ├── validators.en.xlf
    └── validators.fr.xlf
Download .txt
SYMBOL INDEX (522 symbols across 70 files)

FILE: config/reference.php
  class App (line 1525) | final class App
    method config (line 1532) | public static function config(array $config): array
  class Routes (line 1604) | final class Routes
    method config (line 1611) | public static function config(array $config): array

FILE: migrations/Version20191030113307.php
  class Version20191030113307 (line 13) | final class Version20191030113307 extends AbstractMigration
    method getDescription (line 15) | public function getDescription(): string
    method up (line 20) | public function up(Schema $schema): void
    method down (line 44) | public function down(Schema $schema): void

FILE: migrations/Version20191113170650.php
  class Version20191113170650 (line 13) | final class Version20191113170650 extends AbstractMigration
    method getDescription (line 15) | public function getDescription(): string
    method up (line 20) | public function up(Schema $schema): void
    method down (line 30) | public function down(Schema $schema): void

FILE: migrations/Version20191125093508.php
  class Version20191125093508 (line 13) | final class Version20191125093508 extends AbstractMigration
    method getDescription (line 15) | public function getDescription(): string
    method up (line 20) | public function up(Schema $schema): void
    method down (line 27) | public function down(Schema $schema): void

FILE: migrations/Version20191202091507.php
  class Version20191202091507 (line 13) | final class Version20191202091507 extends AbstractMigration
    method getDescription (line 15) | public function getDescription(): string
    method up (line 20) | public function up(Schema $schema): void
    method down (line 27) | public function down(Schema $schema): void

FILE: migrations/Version20191203111729.php
  class Version20191203111729 (line 13) | final class Version20191203111729 extends AbstractMigration
    method getDescription (line 15) | public function getDescription(): string
    method up (line 20) | public function up(Schema $schema): void
    method down (line 27) | public function down(Schema $schema): void

FILE: migrations/Version20210928132307.php
  class Version20210928132307 (line 13) | final class Version20210928132307 extends AbstractMigration
    method getDescription (line 15) | public function getDescription(): string
    method up (line 20) | public function up(Schema $schema): void
    method down (line 27) | public function down(Schema $schema): void

FILE: migrations/Version20221106220411.php
  class Version20221106220411 (line 13) | final class Version20221106220411 extends AbstractMigration
    method getDescription (line 15) | public function getDescription(): string
    method up (line 20) | public function up(Schema $schema): void
    method down (line 40) | public function down(Schema $schema): void

FILE: migrations/Version20221106220412.php
  class Version20221106220412 (line 13) | final class Version20221106220412 extends AbstractMigration
    method getDescription (line 15) | public function getDescription(): string
    method up (line 20) | public function up(Schema $schema): void
    method down (line 69) | public function down(Schema $schema): void

FILE: migrations/Version20221211154443.php
  class Version20221211154443 (line 13) | final class Version20221211154443 extends AbstractMigration
    method getDescription (line 15) | public function getDescription(): string
    method up (line 20) | public function up(Schema $schema): void
    method down (line 49) | public function down(Schema $schema): void

FILE: migrations/Version20230209142217.php
  class Version20230209142217 (line 13) | final class Version20230209142217 extends AbstractMigration
    method getDescription (line 15) | public function getDescription(): string
    method up (line 20) | public function up(Schema $schema): void
    method down (line 41) | public function down(Schema $schema): void

FILE: migrations/Version20231001214111.php
  class Version20231001214111 (line 13) | final class Version20231001214111 extends AbstractMigration
    method getDescription (line 15) | public function getDescription(): string
    method up (line 20) | public function up(Schema $schema): void
    method down (line 29) | public function down(Schema $schema): void

FILE: migrations/Version20231001214112.php
  class Version20231001214112 (line 13) | final class Version20231001214112 extends AbstractMigration
    method getDescription (line 15) | public function getDescription(): string
    method up (line 20) | public function up(Schema $schema): void
    method down (line 29) | public function down(Schema $schema): void

FILE: migrations/Version20231001214113.php
  class Version20231001214113 (line 13) | final class Version20231001214113 extends AbstractMigration
    method getDescription (line 15) | public function getDescription(): string
    method up (line 20) | public function up(Schema $schema): void
    method down (line 43) | public function down(Schema $schema): void

FILE: migrations/Version20231229203515.php
  class Version20231229203515 (line 15) | final class Version20231229203515 extends AbstractMigration
    method getDescription (line 17) | public function getDescription(): string
    method up (line 22) | public function up(Schema $schema): void
    method down (line 31) | public function down(Schema $schema): void

FILE: migrations/Version20250409193948.php
  class Version20250409193948 (line 13) | final class Version20250409193948 extends AbstractMigration
    method getDescription (line 15) | public function getDescription(): string
    method up (line 20) | public function up(Schema $schema): void
    method down (line 43) | public function down(Schema $schema): void

FILE: migrations/Version20250421163214.php
  class Version20250421163214 (line 13) | final class Version20250421163214 extends AbstractMigration
    method getDescription (line 15) | public function getDescription(): string
    method up (line 20) | public function up(Schema $schema): void
    method down (line 33) | public function down(Schema $schema): void

FILE: migrations/Version20260131161930.php
  class Version20260131161930 (line 13) | final class Version20260131161930 extends AbstractMigration
    method getDescription (line 15) | public function getDescription(): string
    method up (line 20) | public function up(Schema $schema): void
    method down (line 41) | public function down(Schema $schema): void

FILE: src/Command/ApiGenerateCommand.php
  class ApiGenerateCommand (line 11) | #[AsCommand(
    method __construct (line 18) | public function __construct()
    method configure (line 23) | protected function configure(): void
    method execute (line 32) | protected function execute(InputInterface $input, OutputInterface $out...

FILE: src/Command/SyncBirthdayCalendars.php
  class SyncBirthdayCalendars (line 15) | class SyncBirthdayCalendars extends Command
    method __construct (line 17) | public function __construct(
    method configure (line 28) | protected function configure(): void
    method execute (line 38) | protected function execute(InputInterface $input, OutputInterface $out...

FILE: src/Constants.php
  class Constants (line 7) | class Constants

FILE: src/Controller/Admin/AddressBookController.php
  class AddressBookController (line 17) | #[Route('/addressbooks', name: 'addressbook_')]
    method addressBooks (line 20) | #[Route('/{userId}', name: 'index')]
    method addressbookCreate (line 41) | #[Route('/{userId}/new', name: 'create')]
    method addressbookDelete (line 109) | #[Route('/{userId}/delete/{id}', name: 'delete', requirements: ['id' =...

FILE: src/Controller/Admin/CalendarController.php
  class CalendarController (line 22) | #[Route('/calendars', name: 'calendar_')]
    method calendars (line 25) | #[Route('/{userId}', name: 'index')]
    method calendarEdit (line 78) | #[Route('/{userId}/new', name: 'create')]
    method calendarShares (line 173) | #[Route('/{userId}/shares/{calendarid}', name: 'shares', requirements:...
    method calendarShareAdd (line 193) | #[Route('/{userId}/share/{instanceid}', name: 'share_add', requirement...
    method calendarDelete (line 239) | #[Route('/{userId}/delete/{id}', name: 'delete', requirements: ['id' =...
    method calendarRevoke (line 285) | #[Route('/{userId}/revoke/{id}', name: 'revoke', requirements: ['id' =...

FILE: src/Controller/Admin/DashboardController.php
  class DashboardController (line 15) | class DashboardController extends AbstractController
    method dashboard (line 17) | #[Route('/dashboard', name: 'dashboard')]

FILE: src/Controller/Admin/UserController.php
  class UserController (line 22) | #[Route('/users', name: 'user_')]
    method users (line 25) | #[Route('/', name: 'index')]
    method userCreate (line 35) | #[Route('/new', name: 'create')]
    method userDelete (line 127) | #[Route('/delete/{userId}', name: 'delete')]
    method userDelegates (line 195) | #[Route('/delegates/{userId}', name: 'delegates')]
    method userToggleDelegation (line 223) | #[Route('/delegation/{userId}/{toggle}', name: 'delegation_toggle', re...
    method userDelegateAdd (line 267) | #[Route('/delegates/{userId}/add', name: 'delegate_add')]
    method userDelegateRemove (line 312) | #[Route('/delegates/{userId}/remove/{principalProxyId}/{delegateId}', ...

FILE: src/Controller/Api/ApiController.php
  class ApiController (line 18) | #[Route('/api/v1', name: 'api_v1_')]
    method validateUsername (line 28) | private function validateUsername(string $username): bool
    method getTimestamp (line 38) | private function getTimestamp(): string
    method resolveUser (line 48) | private function resolveUser(ManagerRegistry $doctrine, int $userId): ...
    method healthCheck (line 60) | #[Route('/health', name: 'health', methods: ['GET'])]
    method getUsers (line 73) | #[Route('/users', name: 'users', methods: ['GET'])]
    method getUserDetails (line 106) | #[Route('/users/{userId}', name: 'user_detail', methods: ['GET'], requ...
    method getUserCalendars (line 146) | #[Route('/calendars/{userId}', name: 'calendars', methods: ['GET'], re...
    method getUserCalendarDetails (line 225) | #[Route('/calendars/{userId}/{calendar_id}', name: 'calendar_details',...
    method createNewUserCalendar (line 283) | #[Route('/calendars/{userId}/create', name: 'calendar_create', methods...
    method editUserCalendar (line 386) | #[Route('/calendars/{userId}/{calendar_id}', name: 'calendar_edit', me...
    method deleteUserCalendar (line 473) | #[Route('/calendars/{userId}/{calendar_id}', name: 'calendar_delete', ...
    method getUserCalendarsShares (line 542) | #[Route('/calendars/{userId}/shares/{calendar_id}', name: 'calendars_s...
    method setUserCalendarsShare (line 603) | #[Route('/calendars/{userId}/share/{calendar_id}/add', name: 'calendar...
    method removeUserCalendarsShare (line 681) | #[Route('/calendars/{userId}/share/{calendar_id}/remove', name: 'calen...

FILE: src/Controller/DAVController.php
  class DAVController (line 25) | class DAVController extends AbstractController
    method __construct (line 152) | public function __construct(MailerInterface $mailer, BasicAuth $basicA...
    method home (line 180) | #[Route('/', name: 'home')]
    method initServer (line 188) | private function initServer(string $authMethod, string $authRealm = Us...
    method initExceptionListener (line 300) | private function initExceptionListener()
    method dav (line 315) | #[Route('/dav/{path}', name: 'dav', requirements: ['path' => '.*'])]

FILE: src/Controller/SecurityController.php
  class SecurityController (line 10) | class SecurityController extends AbstractController
    method login (line 12) | #[Route('/login', name: 'app_login')]
    method logout (line 27) | #[Route('/logout', name: 'app_logout')]

FILE: src/DataFixtures/AppFixtures.php
  class AppFixtures (line 13) | class AppFixtures extends Fixture
    method load (line 15) | public function load(ObjectManager $manager): void

FILE: src/Entity/AddressBook.php
  class AddressBook (line 11) | #[ORM\Entity()]
    method __construct (line 46) | public function __construct()
    method getId (line 54) | public function getId(): ?int
    method getPrincipalUri (line 59) | public function getPrincipalUri(): ?string
    method setPrincipalUri (line 64) | public function setPrincipalUri(string $principalUri): self
    method getDisplayName (line 71) | public function getDisplayName(): ?string
    method setDisplayName (line 76) | public function setDisplayName(string $displayName): self
    method isIncludedInBirthdayCalendar (line 83) | public function isIncludedInBirthdayCalendar(): ?bool
    method setIncludedInBirthdayCalendar (line 88) | public function setIncludedInBirthdayCalendar(bool $includedInBirthday...
    method getUri (line 95) | public function getUri(): ?string
    method setUri (line 100) | public function setUri(string $uri): self
    method getDescription (line 107) | public function getDescription(): ?string
    method setDescription (line 112) | public function setDescription(string $description): self
    method getSynctoken (line 119) | public function getSynctoken(): ?string
    method setSynctoken (line 124) | public function setSynctoken(string $synctoken): self
    method getCards (line 134) | public function getCards(): Collection
    method addCard (line 139) | public function addCard(Card $card): self
    method removeCard (line 149) | public function removeCard(Card $card): self
    method getChanges (line 165) | public function getChanges(): Collection
    method addChange (line 170) | public function addChange(AddressBookChange $change): self
    method removeChange (line 180) | public function removeChange(AddressBookChange $change): self

FILE: src/Entity/AddressBookChange.php
  class AddressBookChange (line 7) | #[ORM\Entity()]
    method getId (line 29) | public function getId(): ?int
    method getUri (line 34) | public function getUri(): ?string
    method setUri (line 39) | public function setUri(string $uri): self
    method getSynctoken (line 46) | public function getSynctoken(): ?string
    method setSynctoken (line 51) | public function setSynctoken(string $synctoken): self
    method getAddressBook (line 58) | public function getAddressBook(): ?AddressBook
    method setAddressBook (line 63) | public function setAddressBook(?AddressBook $addressBook): self
    method getOperation (line 70) | public function getOperation(): ?int
    method setOperation (line 75) | public function setOperation(int $operation): self

FILE: src/Entity/Calendar.php
  class Calendar (line 9) | #[ORM\Entity()]
    method __construct (line 37) | public function __construct()
    method getId (line 45) | public function getId(): ?int
    method getSynctoken (line 50) | public function getSynctoken(): ?string
    method setSynctoken (line 55) | public function setSynctoken(string $synctoken): self
    method getComponents (line 62) | public function getComponents(): ?string
    method setComponents (line 67) | public function setComponents(?string $components): self
    method getObjects (line 77) | public function getObjects(): Collection
    method addObject (line 82) | public function addObject(CalendarObject $object): self
    method removeObject (line 92) | public function removeObject(CalendarObject $object): self
    method getChanges (line 108) | public function getChanges(): Collection
    method addChange (line 113) | public function addChange(CalendarChange $change): self
    method removeChange (line 123) | public function removeChange(CalendarChange $change): self
    method getInstances (line 139) | public function getInstances(): Collection
    method isComponentEnabled (line 151) | public function isComponentEnabled(string $componentType): bool

FILE: src/Entity/CalendarChange.php
  class CalendarChange (line 7) | #[ORM\Entity()]
    method getId (line 29) | public function getId(): ?int
    method getUri (line 34) | public function getUri(): ?string
    method setUri (line 39) | public function setUri(string $uri): self
    method getSynctoken (line 46) | public function getSynctoken(): ?int
    method setSynctoken (line 51) | public function setSynctoken(int $synctoken): self
    method getCalendar (line 58) | public function getCalendar(): ?Calendar
    method setCalendar (line 63) | public function setCalendar(?Calendar $calendar): self
    method getOperation (line 70) | public function getOperation(): ?int
    method setOperation (line 75) | public function setOperation(int $operation): self

FILE: src/Entity/CalendarInstance.php
  class CalendarInstance (line 11) | #[ORM\Entity(repositoryClass: "App\Repository\CalendarInstanceRepository")]
    method getOwnerAccesses (line 16) | public static function getOwnerAccesses(): array
    method __construct (line 74) | public function __construct()
    method getId (line 83) | public function getId(): ?int
    method getCalendar (line 88) | public function getCalendar(): ?Calendar
    method setCalendar (line 93) | public function setCalendar(?Calendar $calendar): self
    method getPrincipalUri (line 100) | public function getPrincipalUri(): ?string
    method setPrincipalUri (line 105) | public function setPrincipalUri(?string $principalUri): self
    method getAccess (line 112) | public function getAccess(): ?int
    method setAccess (line 117) | public function setAccess(int $access): self
    method isShared (line 124) | public function isShared(): bool
    method setPublic (line 129) | public function setPublic(bool $public): self
    method isPublic (line 136) | public function isPublic(): bool
    method isAutomaticallyGenerated (line 141) | public function isAutomaticallyGenerated(): bool
    method getDisplayName (line 146) | public function getDisplayName(): ?string
    method setDisplayName (line 151) | public function setDisplayName(?string $displayName): self
    method getUri (line 158) | public function getUri(): ?string
    method setUri (line 163) | public function setUri(?string $uri): self
    method getDescription (line 170) | public function getDescription(): ?string
    method setDescription (line 175) | public function setDescription(?string $description): self
    method getCalendarOrder (line 182) | public function getCalendarOrder(): ?int
    method setCalendarOrder (line 187) | public function setCalendarOrder(int $calendarOrder): self
    method getCalendarColor (line 194) | public function getCalendarColor(): ?string
    method setCalendarColor (line 199) | public function setCalendarColor(?string $calendarColor): self
    method getTimezone (line 206) | public function getTimezone(): ?string
    method setTimezone (line 211) | public function setTimezone(?string $timezone): self
    method getTransparent (line 218) | public function getTransparent(): ?int
    method setTransparent (line 223) | public function setTransparent(?int $transparent): self
    method getShareHref (line 230) | public function getShareHref(): ?string
    method setShareHref (line 235) | public function setShareHref(?string $shareHref): self
    method getShareDisplayName (line 242) | public function getShareDisplayName(): ?string
    method setShareDisplayName (line 247) | public function setShareDisplayName(?string $shareDisplayName): self
    method getShareInviteStatus (line 254) | public function getShareInviteStatus(): ?int
    method setShareInviteStatus (line 259) | public function setShareInviteStatus(int $shareInviteStatus): self

FILE: src/Entity/CalendarObject.php
  class CalendarObject (line 7) | #[ORM\Entity()]
    method getId (line 50) | public function getId(): ?int
    method getCalendarData (line 55) | public function getCalendarData(): ?string
    method setCalendarData (line 60) | public function setCalendarData(?string $calendarData): self
    method getUri (line 67) | public function getUri(): ?string
    method setUri (line 72) | public function setUri(?string $uri): self
    method getCalendar (line 79) | public function getCalendar(): ?Calendar
    method setCalendar (line 84) | public function setCalendar(?Calendar $calendar): self
    method getLastModifier (line 91) | public function getLastModifier(): ?int
    method setLastModifier (line 96) | public function setLastModifier(?int $lastModifier): self
    method getEtag (line 103) | public function getEtag(): ?string
    method setEtag (line 108) | public function setEtag(?string $etag): self
    method getSize (line 115) | public function getSize(): ?int
    method setSize (line 120) | public function setSize(int $size): self
    method getComponentType (line 127) | public function getComponentType(): ?string
    method setComponentType (line 132) | public function setComponentType(?string $componentType): self
    method getFirstOccurence (line 139) | public function getFirstOccurence(): ?int
    method setFirstOccurence (line 144) | public function setFirstOccurence(?int $firstOccurence): self
    method getLastOccurence (line 151) | public function getLastOccurence(): ?int
    method setLastOccurence (line 156) | public function setLastOccurence(?int $lastOccurence): self
    method getUid (line 163) | public function getUid(): ?string
    method setUid (line 168) | public function setUid(?string $uid): self
    method getLastModified (line 175) | public function getLastModified(): ?int
    method setLastModified (line 180) | public function setLastModified(?int $lastModified): self

FILE: src/Entity/CalendarSubscription.php
  class CalendarSubscription (line 7) | #[ORM\Entity()]
    method getId (line 49) | public function getId(): ?int
    method getUri (line 54) | public function getUri(): ?string
    method setUri (line 59) | public function setUri(string $uri): self
    method getPrincipalUri (line 66) | public function getPrincipalUri(): ?string
    method setPrincipalUri (line 71) | public function setPrincipalUri(string $principalUri): self
    method getSource (line 78) | public function getSource(): ?string
    method setSource (line 83) | public function setSource(?string $source): self
    method getDisplayName (line 90) | public function getDisplayName(): ?string
    method setDisplayName (line 95) | public function setDisplayName(?string $displayName): self
    method getRefreshRate (line 102) | public function getRefreshRate(): ?string
    method setRefreshRate (line 107) | public function setRefreshRate(?string $refreshRate): self
    method getCalendarOrder (line 114) | public function getCalendarOrder(): ?int
    method setCalendarOrder (line 119) | public function setCalendarOrder(int $calendarOrder): self
    method getCalendarColor (line 126) | public function getCalendarColor(): ?string
    method setCalendarColor (line 131) | public function setCalendarColor(?string $calendarColor): self
    method getStripTodos (line 138) | public function getStripTodos(): ?int
    method setStripTodos (line 143) | public function setStripTodos(?int $stripTodos): self
    method getStripAlarms (line 150) | public function getStripAlarms(): ?int
    method setStripAlarms (line 155) | public function setStripAlarms(?int $stripAlarms): self
    method getStripAttachments (line 162) | public function getStripAttachments(): ?int
    method setStripAttachments (line 167) | public function setStripAttachments(?int $stripAttachments): self
    method getLastModified (line 174) | public function getLastModified(): ?int
    method setLastModified (line 179) | public function setLastModified(?int $lastModified): self

FILE: src/Entity/Card.php
  class Card (line 7) | #[ORM\Entity()]
    method getId (line 38) | public function getId(): ?int
    method getAddressBook (line 43) | public function getAddressBook(): ?AddressBook
    method setAddressBook (line 48) | public function setAddressBook(?AddressBook $addressBook): self
    method getCardData (line 55) | public function getCardData(): ?string
    method setCardData (line 60) | public function setCardData(?string $cardData): self
    method getUri (line 67) | public function getUri(): ?string
    method setUri (line 72) | public function setUri(?string $uri): self
    method getLastModified (line 79) | public function getLastModified(): ?int
    method setLastModified (line 84) | public function setLastModified(?int $lastModified): self
    method getEtag (line 91) | public function getEtag(): ?string
    method setEtag (line 96) | public function setEtag(?string $etag): self
    method getSize (line 103) | public function getSize(): ?int
    method setSize (line 108) | public function setSize(int $size): self

FILE: src/Entity/Lock.php
  class Lock (line 7) | #[ORM\Entity()]
    method getId (line 37) | public function getId(): ?int
    method getOwner (line 42) | public function getOwner(): ?string
    method setOwner (line 47) | public function setOwner(?string $owner): self
    method getTimeout (line 54) | public function getTimeout(): ?int
    method setTimeout (line 59) | public function setTimeout(?int $timeout): self
    method getCreated (line 66) | public function getCreated(): ?int
    method setCreated (line 71) | public function setCreated(?int $created): self
    method getToken (line 78) | public function getToken(): ?string
    method setToken (line 83) | public function setToken(?string $token): self
    method getScope (line 90) | public function getScope(): ?int
    method setScope (line 95) | public function setScope(?int $scope): self
    method getDepth (line 102) | public function getDepth(): ?int
    method setDepth (line 107) | public function setDepth(?int $depth): self
    method getUri (line 114) | public function getUri(): ?string
    method setUri (line 119) | public function setUri(?string $uri): self

FILE: src/Entity/Principal.php
  class Principal (line 11) | #[ORM\Entity(repositoryClass: "App\Repository\PrincipalRepository")]
    method __construct (line 53) | public function __construct()
    method getId (line 60) | public function getId(): ?int
    method getUri (line 65) | public function getUri(): ?string
    method setUri (line 70) | public function setUri(string $uri): self
    method getUsername (line 77) | public function getUsername(): ?string
    method getEmail (line 82) | public function getEmail(): ?string
    method setEmail (line 87) | public function setEmail(?string $email): self
    method getDisplayName (line 94) | public function getDisplayName(): ?string
    method setDisplayName (line 99) | public function setDisplayName(?string $displayName): self
    method getDelegees (line 109) | public function getDelegees(): Collection
    method addDelegee (line 114) | public function addDelegee(Principal $delegee): self
    method removeDelegee (line 123) | public function removeDelegee(Principal $delegee): self
    method removeAllDelegees (line 132) | public function removeAllDelegees(): self
    method getIsMain (line 139) | public function getIsMain(): ?bool
    method setIsMain (line 144) | public function setIsMain(bool $isMain): self
    method getIsAdmin (line 151) | public function getIsAdmin(): ?bool
    method setIsAdmin (line 156) | public function setIsAdmin(bool $isAdmin): self

FILE: src/Entity/PropertyStorage.php
  class PropertyStorage (line 7) | #[ORM\Entity()]
    method getId (line 28) | public function getId(): ?int
    method getPath (line 33) | public function getPath(): ?string
    method setPath (line 38) | public function setPath(string $path): self
    method getName (line 45) | public function getName(): ?string
    method setName (line 50) | public function setName(string $name): self
    method getValueType (line 57) | public function getValueType(): ?int
    method setValueType (line 62) | public function setValueType(?int $valueType): self
    method getValue (line 69) | public function getValue(): ?string
    method setValue (line 74) | public function setValue(?string $value): self

FILE: src/Entity/SchedulingObject.php
  class SchedulingObject (line 8) | #[ORM\Entity()]
    method getId (line 39) | public function getId(): ?int
    method getPrincipalUri (line 44) | public function getPrincipalUri(): ?string
    method setPrincipalUri (line 49) | public function setPrincipalUri(?string $principalUri): self
    method getCalendarData (line 56) | public function getCalendarData(): ?string
    method setCalendarData (line 61) | public function setCalendarData(?string $calendarData): self
    method getUri (line 68) | public function getUri(): ?string
    method setUri (line 73) | public function setUri(?string $uri): self
    method getLastModified (line 80) | public function getLastModified(): ?int
    method setLastModified (line 85) | public function setLastModified(?int $lastModified): self
    method getEtag (line 92) | public function getEtag(): ?string
    method setEtag (line 97) | public function setEtag(?string $etag): self
    method getSize (line 104) | public function getSize(): ?int
    method setSize (line 109) | public function setSize(int $size): self

FILE: src/Entity/User.php
  class User (line 9) | #[ORM\Entity()]
    method getId (line 28) | public function getId(): ?int
    method getUsername (line 33) | public function getUsername(): ?string
    method setUsername (line 38) | public function setUsername(string $username): self
    method getPassword (line 45) | public function getPassword(): ?string
    method setPassword (line 52) | public function setPassword(?string $password): self

FILE: src/Form/AddressBookType.php
  class AddressBookType (line 15) | class AddressBookType extends AbstractType
    method buildForm (line 17) | public function buildForm(FormBuilderInterface $builder, array $option...
    method configureOptions (line 51) | public function configureOptions(OptionsResolver $resolver): void

FILE: src/Form/CalendarInstanceType.php
  class CalendarInstanceType (line 16) | class CalendarInstanceType extends AbstractType
    method buildForm (line 18) | public function buildForm(FormBuilderInterface $builder, array $option...
    method configureOptions (line 81) | public function configureOptions(OptionsResolver $resolver): void

FILE: src/Form/UserType.php
  class UserType (line 16) | class UserType extends AbstractType
    method buildForm (line 18) | public function buildForm(FormBuilderInterface $builder, array $option...
    method configureOptions (line 53) | public function configureOptions(OptionsResolver $resolver): void

FILE: src/Kernel.php
  class Kernel (line 8) | class Kernel extends BaseKernel
    method boot (line 12) | public function boot(): void

FILE: src/Logging/Monolog/PasswordFilterProcessor.php
  class PasswordFilterProcessor (line 8) | final class PasswordFilterProcessor implements ProcessorInterface
    method redactContextRecursive (line 14) | public static function redactContextRecursive(array $context): array
    method __invoke (line 30) | public function __invoke(LogRecord $record): LogRecord

FILE: src/Plugins/BirthdayCalendarPlugin.php
  class BirthdayCalendarPlugin (line 10) | class BirthdayCalendarPlugin extends DAV\ServerPlugin
    method __construct (line 22) | public function __construct(BirthdayService $birthdayService, Calendar...
    method initialize (line 28) | public function initialize(DAV\Server $server)
    method afterCardCreate (line 44) | public function afterCardCreate(string $path, DAV\ICollection $parentN...
    method afterCardUpdate (line 52) | public function afterCardUpdate(string $path, DAV\IFile $node): void
    method beforeCardDelete (line 67) | public function beforeCardDelete(string $path): void
    method handleCardChange (line 87) | private function handleCardChange(string $path, CardDAV\AddressBook $p...
    method getPluginName (line 96) | public function getPluginName(): string

FILE: src/Plugins/DavisIMipPlugin.php
  class DavisIMipPlugin (line 18) | final class DavisIMipPlugin extends SabreBaseIMipPlugin
    method __construct (line 40) | public function __construct(MailerInterface $mailer, string $senderEma...
    method schedule (line 50) | public function schedule(ITip\Message $itip)
    method getPluginInfo (line 266) | public function getPluginInfo()

FILE: src/Plugins/PublicAwareDAVACLPlugin.php
  class PublicAwareDAVACLPlugin (line 10) | class PublicAwareDAVACLPlugin extends \Sabre\DAVACL\Plugin
    method __construct (line 22) | public function __construct(EntityManagerInterface $entityManager, boo...
    method beforeMethod (line 32) | public function beforeMethod(RequestInterface $request, ResponseInterf...
    method getAcl (line 42) | public function getAcl($node): array

FILE: src/Repository/CalendarInstanceRepository.php
  class CalendarInstanceRepository (line 19) | class CalendarInstanceRepository extends ServiceEntityRepository
    method __construct (line 21) | public function __construct(ManagerRegistry $registry)
    method findSharedInstancesOfInstance (line 29) | public function findSharedInstancesOfInstance(int $calendarId, bool $w...
    method findSharedInstanceOfInstanceFor (line 53) | public function findSharedInstanceOfInstanceFor(int $calendarId, strin...
    method hasDifferentOwner (line 66) | public function hasDifferentOwner(int $calendarId, string $principalUr...
    method findAllSchedulingObjectsForCalendar (line 81) | public function findAllSchedulingObjectsForCalendar(int $calendarInsta...
    method getObjectCountsByComponentType (line 104) | public function getObjectCountsByComponentType(int $calendarId): array

FILE: src/Repository/PrincipalRepository.php
  class PrincipalRepository (line 15) | class PrincipalRepository extends ServiceEntityRepository
    method __construct (line 17) | public function __construct(ManagerRegistry $registry)
    method findAllExceptPrincipal (line 25) | public function findAllExceptPrincipal(string $principalUri)
    method findAllMainPrincipalsWithUserIds (line 39) | public function findAllMainPrincipalsWithUserIds(): array

FILE: src/Security/AdminUser.php
  class AdminUser (line 8) | class AdminUser implements UserInterface, PasswordAuthenticatedUserInter...
    method __construct (line 13) | public function __construct(string $username, string $password)
    method getRoles (line 22) | public function getRoles(): array
    method getPassword (line 30) | public function getPassword(): string
    method getSalt (line 42) | public function getSalt()
    method getUsername (line 52) | public function getUsername()
    method getUserIdentifier (line 57) | public function getUserIdentifier(): string
    method eraseCredentials (line 68) | public function eraseCredentials(): void

FILE: src/Security/AdminUserProvider.php
  class AdminUserProvider (line 10) | class AdminUserProvider implements UserProviderInterface
    method loadUserByUsername (line 23) | public function loadUserByUsername($username)
    method loadUserByIdentifier (line 28) | public function loadUserByIdentifier(string $identifier): UserInterface
    method refreshUser (line 44) | public function refreshUser(UserInterface $user): UserInterface
    method supportsClass (line 56) | public function supportsClass($class): bool

FILE: src/Security/ApiKeyAuthenticator.php
  class ApiKeyAuthenticator (line 16) | class ApiKeyAuthenticator extends AbstractAuthenticator
    method __construct (line 20) | public function __construct(string $apiKey)
    method supports (line 30) | public function supports(Request $request): ?bool
    method authenticate (line 42) | public function authenticate(Request $request): Passport
    method onAuthenticationSuccess (line 56) | public function onAuthenticationSuccess(Request $request, TokenInterfa...
    method onAuthenticationFailure (line 61) | public function onAuthenticationFailure(Request $request, Authenticati...

FILE: src/Security/LoginFormAuthenticator.php
  class LoginFormAuthenticator (line 20) | class LoginFormAuthenticator extends AbstractLoginFormAuthenticator
    method __construct (line 30) | public function __construct(UrlGeneratorInterface $urlGenerator, CsrfT...
    method getLoginUrl (line 38) | protected function getLoginUrl(Request $request): string
    method supports (line 43) | public function supports(Request $request): bool
    method authenticate (line 49) | public function authenticate(Request $request): Passport
    method onAuthenticationSuccess (line 77) | public function onAuthenticationSuccess(Request $request, TokenInterfa...

FILE: src/Services/BasicAuth.php
  class BasicAuth (line 9) | final class BasicAuth extends AbstractBasic
    method __construct (line 25) | public function __construct(ManagerRegistry $doctrine, Utils $utils)
    method validateUserPass (line 31) | protected function validateUserPass($username, $password): bool

FILE: src/Services/BirthdayService.php
  class BirthdayService (line 32) | class BirthdayService
    method __construct (line 39) | public function __construct(
    method setBackend (line 45) | public function setBackend(CalendarBackend $calendarBackend)
    method onCardChanged (line 50) | public function onCardChanged(int $addressBookId, string $cardUri, str...
    method onCardDeleted (line 64) | public function onCardDeleted(int $addressBookId, string $cardUri): void
    method shouldBirthdayCalendarExist (line 87) | public function shouldBirthdayCalendarExist(string $principalUri): bool
    method ensureBirthdayCalendarExists (line 96) | public function ensureBirthdayCalendarExists(string $principalUri): Ca...
    method deleteBirthdayCalendar (line 126) | public function deleteBirthdayCalendar(string $principalUri): void
    method buildDataFromContact (line 144) | public function buildDataFromContact(string $cardData): ?VCalendar
    method resetForPrincipal (line 261) | public function resetForPrincipal(string $principal): void
    method syncUser (line 279) | public function syncUser(string $username): void
    method syncPrincipal (line 284) | public function syncPrincipal(string $principal): void
    method birthdayEventChanged (line 308) | public function birthdayEventChanged(string $existingCalendarData, VCa...
    method updateCalendar (line 325) | private function updateCalendar(string $cardUri, string $cardData, Add...

FILE: src/Services/IMAPAuth.php
  class IMAPAuth (line 11) | final class IMAPAuth extends AbstractBasic
    method __construct (line 63) | public function __construct(ManagerRegistry $doctrine, Utils $utils, s...
    method imapOpen (line 105) | protected function imapOpen(string $username, string $password): bool
    method validateUserPass (line 153) | protected function validateUserPass($username, $password): bool

FILE: src/Services/LDAPAuth.php
  class LDAPAuth (line 9) | final class LDAPAuth extends AbstractBasic
    method __construct (line 74) | public function __construct(ManagerRegistry $doctrine, Utils $utils, s...
    method ldapOpen (line 94) | protected function ldapOpen($username, $password)
    method validateUserPass (line 229) | protected function validateUserPass($username, $password): bool

FILE: src/Services/Utils.php
  class Utils (line 13) | final class Utils
    method __construct (line 36) | public function __construct(ManagerRegistry $doctrine, TranslatorInter...
    method hashPassword (line 47) | public function hashPassword(string $username, string $password): string
    method createPasswordlessUserWithDefaultObjects (line 52) | public function createPasswordlessUserWithDefaultObjects(string $usern...

FILE: src/Version.php
  class Version (line 5) | final class Version

FILE: tests/Functional/Commands/SyncBirthdayCalendarTest.php
  class SyncBirthdayCalendarTest (line 21) | class SyncBirthdayCalendarTest extends KernelTestCase
    method setUp (line 26) | protected function setUp(): void
    method tearDown (line 43) | protected function tearDown(): void
    method createUser (line 49) | private function createUser(string $username): User
    method createAddressBookWithCard (line 67) | private function createAddressBookWithCard(string $username, string $c...
    method assertBirthdayEventExists (line 92) | private function assertBirthdayEventExists(string $principalUri, strin...
    method testExecuteSyncsAllUsers (line 109) | public function testExecuteSyncsAllUsers(): void
    method testExecuteSyncsSingleUser (line 135) | public function testExecuteSyncsSingleUser(): void
    method testExecuteThrowsExceptionForUnknownUser (line 168) | public function testExecuteThrowsExceptionForUnknownUser(): void
    method testExecuteWithNoUsersInDatabaseSucceeds (line 176) | public function testExecuteWithNoUsersInDatabaseSucceeds(): void

FILE: tests/Functional/Controllers/AddressBookControllerTest.php
  class AddressBookControllerTest (line 10) | class AddressBookControllerTest extends WebTestCase
    method getUserId (line 12) | private function getUserId($client, string $username): int
    method testAddressBookIndex (line 20) | public function testAddressBookIndex(): void
    method testAddressBookEdit (line 39) | public function testAddressBookEdit(): void
    method testAddressBookNew (line 66) | public function testAddressBookNew(): void
    method testAddressBookDelete (line 98) | public function testAddressBookDelete(): void

FILE: tests/Functional/Controllers/ApiControllerTest.php
  class ApiControllerTest (line 7) | class ApiControllerTest extends WebTestCase
    method getUserId (line 17) | private function getUserId($client, int $index): int
    method getUserUsername (line 43) | private function getUserUsername($client, int $index): string
    method getCalendarId (line 70) | private function getCalendarId($client, int $userId, bool $default = t...
    method testHealth (line 94) | public function testHealth(): void
    method testApiInvalidToken (line 109) | public function testApiInvalidToken(): void
    method testApiMissingToken (line 127) | public function testApiMissingToken(): void
    method testUserList (line 144) | public function testUserList(): void
    method testUserDetails (line 177) | public function testUserDetails(): void
    method testUserCalendarsList (line 209) | public function testUserCalendarsList(): void
    method testUserCalendarDetails (line 236) | public function testUserCalendarDetails(): void
    method testCreateUserCalendar (line 280) | public function testCreateUserCalendar(): void
    method testEditUserCalendar (line 334) | public function testEditUserCalendar(): void
    method testGetUserCalendarSharesEmpty (line 384) | public function testGetUserCalendarSharesEmpty(): void
    method testShareUserCalendar (line 408) | public function testShareUserCalendar(): void
    method testUnshareUserCalendar (line 452) | public function testUnshareUserCalendar(): void
    method testCreateUserCalendarNoComponents (line 491) | public function testCreateUserCalendarNoComponents(): void
    method testEditUserCalendarNoComponents (line 523) | public function testEditUserCalendarNoComponents(): void
    method testDeleteUserCalendar (line 555) | public function testDeleteUserCalendar(): void

FILE: tests/Functional/Controllers/CalendarControllerTest.php
  class CalendarControllerTest (line 10) | class CalendarControllerTest extends WebTestCase
    method getUserId (line 12) | private function getUserId($client, string $username): int
    method testCalendarIndex (line 20) | public function testCalendarIndex(): void
    method testCalendarEdit (line 39) | public function testCalendarEdit(): void
    method testCalendarNew (line 66) | public function testCalendarNew(): void
    method testCalendarDelete (line 99) | public function testCalendarDelete(): void

FILE: tests/Functional/Controllers/DashboardTest.php
  class DashboardTest (line 7) | class DashboardTest extends WebTestCase
    method testIndexPage (line 9) | public function testIndexPage(): void
    method testDashboardPageUnlogged (line 22) | public function testDashboardPageUnlogged(): void
    method testLoginPage (line 30) | public function testLoginPage(): void
    method testLoginIncorrectUsername (line 40) | public function testLoginIncorrectUsername(): void
    method testLoginIncorrectPassword (line 57) | public function testLoginIncorrectPassword(): void
    method testLoginCorrect (line 74) | public function testLoginCorrect(): void

FILE: tests/Functional/Controllers/UserControllerTest.php
  class UserControllerTest (line 9) | class UserControllerTest extends WebTestCase
    method getUserId (line 11) | private function getUserId($client, string $username): int
    method testUserIndex (line 19) | public function testUserIndex(): void
    method testUserEdit (line 35) | public function testUserEdit(): void
    method testUserNew (line 59) | public function testUserNew(): void
    method testUserDelete (line 91) | public function testUserDelete(): void
    method testUserDelegates (line 116) | public function testUserDelegates(): void

FILE: tests/Functional/DavTest.php
  class DavTest (line 8) | class DavTest extends WebTestCase
    method requestDavClient (line 15) | public static function requestDavClient(string $method, string $path):...
    method testUnauthorized (line 26) | public function testUnauthorized(): void

FILE: tests/Functional/Service/BirthdayServiceTest.php
  class BirthdayServiceTest (line 18) | class BirthdayServiceTest extends KernelTestCase
    method setUp (line 23) | protected function setUp(): void
    method tearDown (line 36) | protected function tearDown(): void
    method createAddressBook (line 42) | private function createAddressBook(
    method testBuildDataFromContactReturnsNullForEmptyData (line 69) | public function testBuildDataFromContactReturnsNullForEmptyData(): void
    method testBuildDataFromContactReturnsNullIfNoBday (line 74) | public function testBuildDataFromContactReturnsNullIfNoBday(): void
    method testBuildDataFromContactReturnsNullIfNoFn (line 80) | public function testBuildDataFromContactReturnsNullIfNoFn(): void
    method testBuildDataFromContactReturnsVCalendarWithBday (line 86) | public function testBuildDataFromContactReturnsVCalendarWithBday(): void
    method testBuildDataFromContactHandlesLeapDay (line 98) | public function testBuildDataFromContactHandlesLeapDay(): void
    method testBuildDataFromContactHandlesOmitYear (line 107) | public function testBuildDataFromContactHandlesOmitYear(): void
    method testBuildDataFromContactAddsAlarm (line 116) | public function testBuildDataFromContactAddsAlarm(): void
    method testBirthdayEventChangedReturnsFalseWhenSame (line 128) | public function testBirthdayEventChangedReturnsFalseWhenSame(): void
    method testBirthdayEventChangedReturnsTrueWhenDifferentDate (line 135) | public function testBirthdayEventChangedReturnsTrueWhenDifferentDate()...
    method testBirthdayEventChangedReturnsTrueWhenDifferentName (line 143) | public function testBirthdayEventChangedReturnsTrueWhenDifferentName()...
    method testBirthdayEventChangedReturnsTrueOnInvalidExistingData (line 151) | public function testBirthdayEventChangedReturnsTrueOnInvalidExistingDa...
    method testOnCardChangedSkipsIfNotIncludedInBirthdayCalendar (line 162) | public function testOnCardChangedSkipsIfNotIncludedInBirthdayCalendar(...
    method testOnCardChangedCreatesCalendarObject (line 178) | public function testOnCardChangedCreatesCalendarObject(): void
    method testOnCardChangedUpdatesExistingCalendarObject (line 195) | public function testOnCardChangedUpdatesExistingCalendarObject(): void
    method testOnCardChangedDoesNotCreateObjectIfNoBday (line 240) | public function testOnCardChangedDoesNotCreateObjectIfNoBday(): void
    method testOnCardDeletedRemovesCalendarObject (line 260) | public function testOnCardDeletedRemovesCalendarObject(): void
    method testOnCardDeletedIsNoopIfNoCalendarObject (line 278) | public function testOnCardDeletedIsNoopIfNoCalendarObject(): void
    method testEnsureBirthdayCalendarExistsCreatesCalendar (line 292) | public function testEnsureBirthdayCalendarExistsCreatesCalendar(): void
    method testEnsureBirthdayCalendarExistsIsIdempotent (line 305) | public function testEnsureBirthdayCalendarExistsIsIdempotent(): void
    method testSyncPrincipalDeletesBirthdayCalendarIfNoAddressBooksIncluded (line 322) | public function testSyncPrincipalDeletesBirthdayCalendarIfNoAddressBoo...
Condensed preview — 174 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (805K chars).
[
  {
    "path": ".dockerignore",
    "chars": 165,
    "preview": "_screenshots\n.DS_Store\nREADME.md\nLICENSE\n.git\n.gitignore\n.github\n.env.local\n.env.test.local\nphpunit.xml.dist\n.php-cs*\n.p"
  },
  {
    "path": ".github/FUNDING.yml",
    "chars": 40,
    "preview": "custom: ['https://www.paypal.me/tchap']\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.yml",
    "chars": 4352,
    "preview": "name: Bug Report\ndescription: Something is not working as expected\nlabels: [\"bug\"]\nbody:\n  - type: markdown\n    attribut"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.yml",
    "chars": 1528,
    "preview": "name: Feature Request\ndescription: Suggest an improvement or new functionality\nlabels: [\"feature request\"]\nbody:\n  - typ"
  },
  {
    "path": ".github/workflows/ci.yml",
    "chars": 9071,
    "preview": "name: CI\n\non:\n  push:\n    branches:\n      - main  # Only run on pushes to main\n  pull_request:\n    # Run on all PRs (no "
  },
  {
    "path": ".github/workflows/main.yml",
    "chars": 6134,
    "preview": "name: Publish Docker image\n\non:\n  workflow_dispatch:\n  release:\n    types: [published]\n\nconcurrency:\n  group: ${{ github"
  },
  {
    "path": ".gitignore",
    "chars": 411,
    "preview": "###> symfony/framework-bundle ###\n/.env.local\n/.env.local.php\n/.env.*.local\n/config/secrets/prod/prod.decrypt.private.ph"
  },
  {
    "path": ".hadolint.yaml",
    "chars": 130,
    "preview": "ignored:\n  - DL3018  # We don't pin apk versions (Alpine is rolling)\nfailure-threshold: error  # Only fail on errors, no"
  },
  {
    "path": ".php-cs-fixer.php",
    "chars": 813,
    "preview": "<?php\n\n$finder = (new PhpCsFixer\\Finder())\n    ->in(__DIR__)\n    ->exclude('var')\n;\n\nreturn (new PhpCsFixer\\Config())\n  "
  },
  {
    "path": "LICENSE",
    "chars": 1062,
    "preview": "MIT License\n\nCopyright (c) 2019 tchap\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof t"
  },
  {
    "path": "README.md",
    "chars": 25007,
    "preview": "Davis\n---\n\n[![Build Status][ci_badge]][ci_link]\n[![Publish Docker image](https://github.com/tchapi/davis/actions/workflo"
  },
  {
    "path": "bin/console",
    "chars": 1084,
    "preview": "#!/usr/bin/env php\n<?php\n\n$overridenEnvDir = getenv('ENV_DIR') ?: null;\n\nif ($overridenEnvDir) {\n    // Tell the Runtime"
  },
  {
    "path": "bin/phpunit",
    "chars": 642,
    "preview": "#!/usr/bin/env php\n<?php\n\nif (!ini_get('date.timezone')) {\n    ini_set('date.timezone', 'UTC');\n}\n\nif (is_file(dirname(_"
  },
  {
    "path": "composer.json",
    "chars": 2998,
    "preview": "{\n    \"name\": \"tchapi/davis\",\n    \"description\": \"A simple, fully translatable admin interface and frontend for sabre/da"
  },
  {
    "path": "config/bundles.php",
    "chars": 836,
    "preview": "<?php\n\nreturn [\n    Symfony\\Bundle\\FrameworkBundle\\FrameworkBundle::class => ['all' => true],\n    Doctrine\\Bundle\\Doctri"
  },
  {
    "path": "config/packages/cache.yaml",
    "chars": 687,
    "preview": "framework:\n    cache:\n        # Unique name of your app: used to compute stable namespaces for cache keys.\n        #pref"
  },
  {
    "path": "config/packages/dev/debug.yaml",
    "chars": 235,
    "preview": "debug:\n    # Forwards VarDumper Data clones to a centralized server allowing to inspect dumps on CLI or in your browser."
  },
  {
    "path": "config/packages/dev/monolog.yaml",
    "chars": 606,
    "preview": "monolog:\n    handlers:\n        main:\n            type: stream\n            path: \"%kernel.logs_dir%/%kernel.environment%."
  },
  {
    "path": "config/packages/dev/web_profiler.yaml",
    "chars": 116,
    "preview": "web_profiler:\n    toolbar: true\n    intercept_redirects: false\n\nframework:\n    profiler: { only_exceptions: false }\n"
  },
  {
    "path": "config/packages/doctrine.yaml",
    "chars": 798,
    "preview": "doctrine:\n    dbal:\n        # The server_version must be configured directly in the\n        # DATABASE_URL to allow diff"
  },
  {
    "path": "config/packages/doctrine_migrations.yaml",
    "chars": 241,
    "preview": "doctrine_migrations:\n    migrations_paths:\n        # namespace is arbitrary but should be different from App\\Migrations\n"
  },
  {
    "path": "config/packages/framework.yaml",
    "chars": 726,
    "preview": "# see https://symfony.com/doc/current/reference/configuration/framework.html\nframework:\n    secret: '%env(APP_SECRET)%'\n"
  },
  {
    "path": "config/packages/mailer.yaml",
    "chars": 56,
    "preview": "framework:\n    mailer:\n        dsn: '%env(MAILER_DSN)%'\n"
  },
  {
    "path": "config/packages/prod/deprecations.yaml",
    "chars": 315,
    "preview": "# As of Symfony 5.1, deprecations are logged in the dedicated \"deprecation\" channel when it exists\n#monolog:\n#    channe"
  },
  {
    "path": "config/packages/prod/doctrine.yaml",
    "chars": 545,
    "preview": "doctrine:\n    orm:\n        auto_generate_proxy_classes: false\n        metadata_cache_driver:\n            type: pool\n    "
  },
  {
    "path": "config/packages/prod/monolog.yaml",
    "chars": 507,
    "preview": "monolog:\n    handlers:\n        main:\n            type: fingers_crossed\n            action_level: error\n            handl"
  },
  {
    "path": "config/packages/prod/routing.yaml",
    "chars": 57,
    "preview": "framework:\n    router:\n        strict_requirements: null\n"
  },
  {
    "path": "config/packages/routing.yaml",
    "chars": 254,
    "preview": "framework:\n    router:\n        utf8: true\n\n        # Configure how to generate URLs in non-HTTP contexts, such as CLI co"
  },
  {
    "path": "config/packages/security.yaml",
    "chars": 1518,
    "preview": "security:\n    password_hashers:\n        Symfony\\Component\\Security\\Core\\User\\PasswordAuthenticatedUserInterface: 'auto'\n"
  },
  {
    "path": "config/packages/test/framework.yaml",
    "chars": 100,
    "preview": "framework:\n    test: true\n    session:\n        storage_factory_id: session.storage.factory.mock_file"
  },
  {
    "path": "config/packages/test/monolog.yaml",
    "chars": 337,
    "preview": "monolog:\n    handlers:\n        main:\n            type: fingers_crossed\n            action_level: error\n            handl"
  },
  {
    "path": "config/packages/test/twig.yaml",
    "chars": 33,
    "preview": "twig:\n    strict_variables: true\n"
  },
  {
    "path": "config/packages/test/validator.yaml",
    "chars": 67,
    "preview": "framework:\n    validation:\n        not_compromised_password: false\n"
  },
  {
    "path": "config/packages/test/web_profiler.yaml",
    "chars": 109,
    "preview": "web_profiler:\n    toolbar: false\n    intercept_redirects: false\n\nframework:\n    profiler: { collect: false }\n"
  },
  {
    "path": "config/packages/translation.yaml",
    "chars": 144,
    "preview": "framework:\n    default_locale: en\n    translator:\n        default_path: '%kernel.project_dir%/translations'\n        fall"
  },
  {
    "path": "config/packages/twig.yaml",
    "chars": 494,
    "preview": "twig:\n    default_path: '%kernel.project_dir%/templates'\n    debug: '%kernel.debug%'\n    strict_variables: '%kernel.debu"
  },
  {
    "path": "config/packages/validator.yaml",
    "chars": 295,
    "preview": "framework:\n    validation:\n        enable_attributes: true\n        email_validation_mode: html5\n\n        # Enables valid"
  },
  {
    "path": "config/reference.php",
    "chars": 101458,
    "preview": "<?php\n\n// This file is auto-generated and is for apps only. Bundles SHOULD NOT rely on its content.\n\nnamespace Symfony\\C"
  },
  {
    "path": "config/routes/attributes.yaml",
    "chars": 123,
    "preview": "controllers:\n    resource: ../../src/Controller/\n    type: attribute\n\nkernel:\n    resource: App\\Kernel\n    type: attribu"
  },
  {
    "path": "config/routes/dev/framework.yaml",
    "chars": 97,
    "preview": "_errors:\n    resource: '@FrameworkBundle/Resources/config/routing/errors.php'\n    prefix: /_error"
  },
  {
    "path": "config/routes/dev/web_profiler.yaml",
    "chars": 223,
    "preview": "web_profiler_wdt:\n    resource: '@WebProfilerBundle/Resources/config/routing/wdt.php'\n    prefix: /_wdt\n\nweb_profiler_pr"
  },
  {
    "path": "config/routes.yaml",
    "chars": 78,
    "preview": "#index:\n#    path: /\n#    controller: App\\Controller\\DefaultController::index\n"
  },
  {
    "path": "config/services.yaml",
    "chars": 3764,
    "preview": "# This file is the entry point to configure your own services.\n# Files in the packages/ subdirectory configure your depe"
  },
  {
    "path": "docker/Dockerfile",
    "chars": 3768,
    "preview": "# syntax=docker/dockerfile:1\n\n# Initial PHP image is available here:\n# https://github.com/docker-library/php/blob/master"
  },
  {
    "path": "docker/Dockerfile-standalone",
    "chars": 4508,
    "preview": "# Initial PHP image is available here:\n# https://github.com/docker-library/php/blob/master/8.2/alpine3.18/fpm/Dockerfile"
  },
  {
    "path": "docker/configurations/Caddyfile",
    "chars": 1089,
    "preview": "{\n        auto_https off\n}\n\n:9000 {\n\t# Redirect .well-known\n\tredir /.well-known/caldav  /dav/\n\tredir /.well-known/cardda"
  },
  {
    "path": "docker/configurations/nginx.conf",
    "chars": 871,
    "preview": "# This is a very simple / naive configuration for nginx + Davis\n#\n# USE HTTPS IN PRODUCTION\n#\n\nupstream docker-davis {\n "
  },
  {
    "path": "docker/configurations/opcache.ini",
    "chars": 342,
    "preview": "opcache.enable=1\nopcache.jit=1255\nopcache.jit_buffer_size=128M\nopcache.revalidate_freq=60\nopcache.save_comments=1\nopcach"
  },
  {
    "path": "docker/configurations/supervisord.conf",
    "chars": 762,
    "preview": "[supervisord]\nnodaemon=true\nuser=root\npidfile=/run/supervisord.pid\nlogfile=/dev/null\nlogfile_maxbytes=0\n\n[unix_http_serv"
  },
  {
    "path": "docker/docker-compose-postgresql.yml",
    "chars": 1358,
    "preview": "version: \"3.7\"\nname: \"davis-docker\"\n\nservices:\n\n  nginx:\n    image: nginx:1.25-alpine\n    container_name: nginx\n    comm"
  },
  {
    "path": "docker/docker-compose-sqlite.yml",
    "chars": 1094,
    "preview": "version: \"3.7\"\nname: \"davis-docker\"\n\nservices:\n\n  nginx:\n    image: nginx:1.25-alpine\n    container_name: nginx\n    comm"
  },
  {
    "path": "docker/docker-compose-standalone.yml",
    "chars": 959,
    "preview": "version: \"3.7\"\nname: \"davis-docker\"\n\nservices:\n\n  mysql:\n    image: mariadb:10.6.10\n    container_name: mysql\n    enviro"
  },
  {
    "path": "docker/docker-compose.yml",
    "chars": 1402,
    "preview": "version: \"3.7\"\nname: \"davis-docker\"\n\nservices:\n\n  nginx:\n    image: nginx:1.25-alpine\n    container_name: nginx\n    comm"
  },
  {
    "path": "docs/api/README.md",
    "chars": 1627,
    "preview": "# Davis API\n\n## API Version 1\n\n### Open Endpoints\n\nOpen endpoints require no Authentication.\n\n* [Health](v1/health.md) :"
  },
  {
    "path": "docs/api/v1/calendars/all.md",
    "chars": 1733,
    "preview": "# User Calendars\n\nGets a list of all available calendars for a specific user.\n\n**URL** : `/api/v1/calendars/:user_id`\n\n*"
  },
  {
    "path": "docs/api/v1/calendars/create.md",
    "chars": 3394,
    "preview": "# Create User Calendar\n\nCreates a new calendar for a specific user.\n\n**URL** : `/api/v1/calendars/:user_id/create`\n\n**Me"
  },
  {
    "path": "docs/api/v1/calendars/delete.md",
    "chars": 1549,
    "preview": "# Delete User Calendar\n\nDeletes a specific calendar for a specific user.\n\n**URL** : `/api/v1/calendars/:user_id/:calenda"
  },
  {
    "path": "docs/api/v1/calendars/details.md",
    "chars": 1288,
    "preview": "# User Calendar Details\n\nGets a list of all available calendars for a specific user.\n\n**URL** : `/api/v1/calendars/:user"
  },
  {
    "path": "docs/api/v1/calendars/edit.md",
    "chars": 3253,
    "preview": "# Edit User Calendar\n\nEdits an existing calendar for a specific user.\n\n**URL** : `/api/v1/calendars/:user_id/:calendar_i"
  },
  {
    "path": "docs/api/v1/calendars/share_add.md",
    "chars": 2323,
    "preview": "# Share User Calendar\n\nShares (or updates write access) a calendar owned by the specified user to another user.\n\n**URL**"
  },
  {
    "path": "docs/api/v1/calendars/share_remove.md",
    "chars": 2128,
    "preview": "# Remove Share User Calendar\n\nRemoves access to a specific shared calendar for a specific user.\n\n**URL** : `/api/v1/cale"
  },
  {
    "path": "docs/api/v1/calendars/shares.md",
    "chars": 1825,
    "preview": "# User Calendar Shares\n\nGets a list of all users with whom a specific user calendar is shared.\n\n**URL** : `/api/v1/calen"
  },
  {
    "path": "docs/api/v1/health.md",
    "chars": 266,
    "preview": "# Health\n\nUsed to check if the API endpoint is active.\n\n**URL** : `/api/v1/health`\n\n**Method** : `GET`\n\n**Auth required*"
  },
  {
    "path": "docs/api/v1/users/all.md",
    "chars": 851,
    "preview": "# Get Users\n\nGets a list of all available users.\n\n**URL** : `/api/v1/users`\n\n**Method** : `GET`\n\n**Auth required** : YES"
  },
  {
    "path": "docs/api/v1/users/details.md",
    "chars": 1089,
    "preview": "# User Details\n\nGets details about a specific user account.\n\n**URL** : `/api/v1/users/:user_id`\n\n**Method** : `GET`\n\n**A"
  },
  {
    "path": "migrations/Version20191030113307.php",
    "chars": 7594,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace DoctrineMigrations;\n\nuse Doctrine\\DBAL\\Schema\\Schema;\nuse Doctrine\\Migrations"
  },
  {
    "path": "migrations/Version20191113170650.php",
    "chars": 1675,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace DoctrineMigrations;\n\nuse Doctrine\\DBAL\\Schema\\Schema;\nuse Doctrine\\Migrations"
  },
  {
    "path": "migrations/Version20191125093508.php",
    "chars": 949,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace DoctrineMigrations;\n\nuse Doctrine\\DBAL\\Schema\\Schema;\nuse Doctrine\\Migrations"
  },
  {
    "path": "migrations/Version20191202091507.php",
    "chars": 1494,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace DoctrineMigrations;\n\nuse Doctrine\\DBAL\\Schema\\Schema;\nuse Doctrine\\Migrations"
  },
  {
    "path": "migrations/Version20191203111729.php",
    "chars": 1056,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace DoctrineMigrations;\n\nuse Doctrine\\DBAL\\Schema\\Schema;\nuse Doctrine\\Migrations"
  },
  {
    "path": "migrations/Version20210928132307.php",
    "chars": 1011,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace DoctrineMigrations;\n\nuse Doctrine\\DBAL\\Schema\\Schema;\nuse Doctrine\\Migrations"
  },
  {
    "path": "migrations/Version20221106220411.php",
    "chars": 5043,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace DoctrineMigrations;\n\nuse Doctrine\\DBAL\\Schema\\Schema;\nuse Doctrine\\Migrations"
  },
  {
    "path": "migrations/Version20221106220412.php",
    "chars": 10236,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace DoctrineMigrations;\n\nuse Doctrine\\DBAL\\Schema\\Schema;\nuse Doctrine\\Migrations"
  },
  {
    "path": "migrations/Version20221211154443.php",
    "chars": 6407,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace DoctrineMigrations;\n\nuse Doctrine\\DBAL\\Schema\\Schema;\nuse Doctrine\\Migrations"
  },
  {
    "path": "migrations/Version20230209142217.php",
    "chars": 3810,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace DoctrineMigrations;\n\nuse Doctrine\\DBAL\\Schema\\Schema;\nuse Doctrine\\Migrations"
  },
  {
    "path": "migrations/Version20231001214111.php",
    "chars": 1431,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace DoctrineMigrations;\n\nuse Doctrine\\DBAL\\Schema\\Schema;\nuse Doctrine\\Migrations"
  },
  {
    "path": "migrations/Version20231001214112.php",
    "chars": 1645,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace DoctrineMigrations;\n\nuse Doctrine\\DBAL\\Schema\\Schema;\nuse Doctrine\\Migrations"
  },
  {
    "path": "migrations/Version20231001214113.php",
    "chars": 3619,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace DoctrineMigrations;\n\nuse Doctrine\\DBAL\\Schema\\Schema;\nuse Doctrine\\Migrations"
  },
  {
    "path": "migrations/Version20231229203515.php",
    "chars": 1694,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace DoctrineMigrations;\n\nuse Doctrine\\DBAL\\Schema\\Schema;\nuse Doctrine\\Migrations"
  },
  {
    "path": "migrations/Version20250409193948.php",
    "chars": 2061,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace DoctrineMigrations;\n\nuse Doctrine\\DBAL\\Schema\\Schema;\nuse Doctrine\\Migrations"
  },
  {
    "path": "migrations/Version20250421163214.php",
    "chars": 1297,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace DoctrineMigrations;\n\nuse Doctrine\\DBAL\\Schema\\Schema;\nuse Doctrine\\Migrations"
  },
  {
    "path": "migrations/Version20260131161930.php",
    "chars": 2174,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace DoctrineMigrations;\n\nuse Doctrine\\DBAL\\Schema\\Schema;\nuse Doctrine\\Migrations"
  },
  {
    "path": "phpunit.xml.dist",
    "chars": 914,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- https://phpunit.readthedocs.io/en/latest/configuration.html -->\n<phpunit xml"
  },
  {
    "path": "public/.htaccess",
    "chars": 3439,
    "preview": "# Use the front controller as index file. It serves as a fallback solution when\n# every other rewrite/redirect fails (e."
  },
  {
    "path": "public/css/style.css",
    "chars": 1719,
    "preview": "body {\n    padding-top: calc(56px + 30px);\n    -webkit-touch-callout: none; /* iOS Safari */\n    -webkit-user-select: no"
  },
  {
    "path": "public/index.php",
    "chars": 654,
    "preview": "<?php\n\n$overridenEnvDir = getenv('ENV_DIR') ?: null;\n\nif ($overridenEnvDir) {\n    // Tell the Runtime not to touch doten"
  },
  {
    "path": "public/js/app.js",
    "chars": 4951,
    "preview": "'use strict'\n\n// Calendar share modal\nconst shareModal = document.getElementById('shareModal')\nif (shareModal) {\n    sha"
  },
  {
    "path": "public/js/color.mode.toggler.js",
    "chars": 2693,
    "preview": "/* Based on Bootstrap' color mode toggler */\n/*!\n * Color mode toggler for Bootstrap's docs (https://getbootstrap.com/)\n"
  },
  {
    "path": "public/robots.txt",
    "chars": 26,
    "preview": "User-agent: *\nDisallow: /\n"
  },
  {
    "path": "public/site.webmanifest",
    "chars": 263,
    "preview": "{\"name\":\"\",\"short_name\":\"\",\"icons\":[{\"src\":\"/android-chrome-192x192.png\",\"sizes\":\"192x192\",\"type\":\"image/png\"},{\"src\":\"/"
  },
  {
    "path": "src/Command/ApiGenerateCommand.php",
    "chars": 1161,
    "preview": "<?php\n\nnamespace App\\Command;\n\nuse Symfony\\Component\\Console\\Attribute\\AsCommand;\nuse Symfony\\Component\\Console\\Command\\"
  },
  {
    "path": "src/Command/SyncBirthdayCalendars.php",
    "chars": 2164,
    "preview": "<?php\n\nnamespace App\\Command;\n\nuse App\\Entity\\User;\nuse App\\Services\\BirthdayService;\nuse Doctrine\\Persistence\\ManagerRe"
  },
  {
    "path": "src/Constants.php",
    "chars": 174,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace App;\n\nclass Constants\n{\n    public const BIRTHDAY_CALENDAR_URI = 'birthday-ca"
  },
  {
    "path": "src/Controller/Admin/AddressBookController.php",
    "chars": 5560,
    "preview": "<?php\n\nnamespace App\\Controller\\Admin;\n\nuse App\\Entity\\AddressBook;\nuse App\\Entity\\Principal;\nuse App\\Entity\\User;\nuse A"
  },
  {
    "path": "src/Controller/Admin/CalendarController.php",
    "chars": 13129,
    "preview": "<?php\n\nnamespace App\\Controller\\Admin;\n\nuse App\\Entity\\Calendar;\nuse App\\Entity\\CalendarInstance;\nuse App\\Entity\\Calenda"
  },
  {
    "path": "src/Controller/Admin/DashboardController.php",
    "chars": 1661,
    "preview": "<?php\n\nnamespace App\\Controller\\Admin;\n\nuse App\\Entity\\AddressBook;\nuse App\\Entity\\CalendarInstance;\nuse App\\Entity\\Cale"
  },
  {
    "path": "src/Controller/Admin/UserController.php",
    "chars": 15297,
    "preview": "<?php\n\nnamespace App\\Controller\\Admin;\n\nuse App\\Entity\\AddressBook;\nuse App\\Entity\\Calendar;\nuse App\\Entity\\CalendarInst"
  },
  {
    "path": "src/Controller/Api/ApiController.php",
    "chars": 34149,
    "preview": "<?php\n\nnamespace App\\Controller\\Api;\n\nuse App\\Entity\\Calendar;\nuse App\\Entity\\CalendarInstance;\nuse App\\Entity\\CalendarS"
  },
  {
    "path": "src/Controller/DAVController.php",
    "chars": 12780,
    "preview": "<?php\n\nnamespace App\\Controller;\n\nuse App\\Entity\\Principal;\nuse App\\Entity\\User;\nuse App\\Plugins\\BirthdayCalendarPlugin;"
  },
  {
    "path": "src/Controller/SecurityController.php",
    "chars": 1110,
    "preview": "<?php\n\nnamespace App\\Controller;\n\nuse Symfony\\Bundle\\FrameworkBundle\\Controller\\AbstractController;\nuse Symfony\\Componen"
  },
  {
    "path": "src/DataFixtures/AppFixtures.php",
    "chars": 4072,
    "preview": "<?php\n\nnamespace App\\DataFixtures;\n\nuse App\\Entity\\AddressBook;\nuse App\\Entity\\Calendar;\nuse App\\Entity\\CalendarInstance"
  },
  {
    "path": "src/Entity/AddressBook.php",
    "chars": 4553,
    "preview": "<?php\n\nnamespace App\\Entity;\n\nuse Doctrine\\Common\\Collections\\ArrayCollection;\nuse Doctrine\\Common\\Collections\\Collectio"
  },
  {
    "path": "src/Entity/AddressBookChange.php",
    "chars": 1558,
    "preview": "<?php\n\nnamespace App\\Entity;\n\nuse Doctrine\\ORM\\Mapping as ORM;\n\n#[ORM\\Entity()]\n#[ORM\\Table(name: 'addressbookchanges')]"
  },
  {
    "path": "src/Entity/Calendar.php",
    "chars": 3798,
    "preview": "<?php\n\nnamespace App\\Entity;\n\nuse Doctrine\\Common\\Collections\\ArrayCollection;\nuse Doctrine\\Common\\Collections\\Collectio"
  },
  {
    "path": "src/Entity/CalendarChange.php",
    "chars": 1541,
    "preview": "<?php\n\nnamespace App\\Entity;\n\nuse Doctrine\\ORM\\Mapping as ORM;\n\n#[ORM\\Entity()]\n#[ORM\\Table(name: 'calendarchanges')]\ncl"
  },
  {
    "path": "src/Entity/CalendarInstance.php",
    "chars": 6143,
    "preview": "<?php\n\nnamespace App\\Entity;\n\nuse App\\Constants;\nuse Doctrine\\ORM\\Mapping as ORM;\nuse Sabre\\DAV\\Sharing\\Plugin as Sharin"
  },
  {
    "path": "src/Entity/CalendarObject.php",
    "chars": 3812,
    "preview": "<?php\n\nnamespace App\\Entity;\n\nuse Doctrine\\ORM\\Mapping as ORM;\n\n#[ORM\\Entity()]\n#[ORM\\Table(name: 'calendarobjects')]\ncl"
  },
  {
    "path": "src/Entity/CalendarSubscription.php",
    "chars": 3928,
    "preview": "<?php\n\nnamespace App\\Entity;\n\nuse Doctrine\\ORM\\Mapping as ORM;\n\n#[ORM\\Entity()]\n#[ORM\\Table(name: 'calendarsubscriptions"
  },
  {
    "path": "src/Entity/Card.php",
    "chars": 2226,
    "preview": "<?php\n\nnamespace App\\Entity;\n\nuse Doctrine\\ORM\\Mapping as ORM;\n\n#[ORM\\Entity()]\n#[ORM\\Table(name: 'cards')]\nclass Card\n{"
  },
  {
    "path": "src/Entity/Lock.php",
    "chars": 2219,
    "preview": "<?php\n\nnamespace App\\Entity;\n\nuse Doctrine\\ORM\\Mapping as ORM;\n\n#[ORM\\Entity()]\n#[ORM\\Table(name: 'locks')]\nclass Lock\n{"
  },
  {
    "path": "src/Entity/Principal.php",
    "chars": 3575,
    "preview": "<?php\n\nnamespace App\\Entity;\n\nuse Doctrine\\Common\\Collections\\ArrayCollection;\nuse Doctrine\\Common\\Collections\\Collectio"
  },
  {
    "path": "src/Entity/PropertyStorage.php",
    "chars": 1411,
    "preview": "<?php\n\nnamespace App\\Entity;\n\nuse Doctrine\\ORM\\Mapping as ORM;\n\n#[ORM\\Entity()]\n#[ORM\\Table(name: 'propertystorage')]\ncl"
  },
  {
    "path": "src/Entity/SchedulingObject.php",
    "chars": 2312,
    "preview": "<?php\n\nnamespace App\\Entity;\n\nuse Doctrine\\ORM\\Mapping as ORM;\nuse Symfony\\Component\\Validator\\Constraints as Assert;\n\n#"
  },
  {
    "path": "src/Entity/User.php",
    "chars": 1227,
    "preview": "<?php\n\nnamespace App\\Entity;\n\nuse Doctrine\\ORM\\Mapping as ORM;\nuse Symfony\\Bridge\\Doctrine\\Validator\\Constraints\\UniqueE"
  },
  {
    "path": "src/Form/AddressBookType.php",
    "chars": 2065,
    "preview": "<?php\n\nnamespace App\\Form;\n\nuse App\\Entity\\AddressBook;\nuse Symfony\\Component\\Form\\AbstractType;\nuse Symfony\\Component\\F"
  },
  {
    "path": "src/Form/CalendarInstanceType.php",
    "chars": 3253,
    "preview": "<?php\n\nnamespace App\\Form;\n\nuse App\\Entity\\CalendarInstance;\nuse Symfony\\Component\\Form\\AbstractType;\nuse Symfony\\Compon"
  },
  {
    "path": "src/Form/UserType.php",
    "chars": 2210,
    "preview": "<?php\n\nnamespace App\\Form;\n\nuse App\\Entity\\User;\nuse Symfony\\Component\\Form\\AbstractType;\nuse Symfony\\Component\\Form\\Ext"
  },
  {
    "path": "src/Kernel.php",
    "chars": 607,
    "preview": "<?php\n\nnamespace App;\n\nuse Symfony\\Bundle\\FrameworkBundle\\Kernel\\MicroKernelTrait;\nuse Symfony\\Component\\HttpKernel\\Kern"
  },
  {
    "path": "src/Logging/Monolog/PasswordFilterProcessor.php",
    "chars": 1308,
    "preview": "<?php\n\nnamespace App\\Logging\\Monolog;\n\nuse Monolog\\LogRecord;\nuse Monolog\\Processor\\ProcessorInterface;\n\nfinal class Pas"
  },
  {
    "path": "src/Plugins/BirthdayCalendarPlugin.php",
    "chars": 2766,
    "preview": "<?php\n\nnamespace App\\Plugins;\n\nuse App\\Services\\BirthdayService;\nuse Sabre\\CalDAV\\Backend\\PDO as CalendarBackend;\nuse Sa"
  },
  {
    "path": "src/Plugins/DavisIMipPlugin.php",
    "chars": 10028,
    "preview": "<?php\n\nnamespace App\\Plugins;\n\nuse DantSu\\OpenStreetMapStaticAPI\\LatLng;\nuse DantSu\\OpenStreetMapStaticAPI\\Markers;\nuse "
  },
  {
    "path": "src/Plugins/PublicAwareDAVACLPlugin.php",
    "chars": 2532,
    "preview": "<?php\n\nnamespace App\\Plugins;\n\nuse App\\Entity\\CalendarInstance;\nuse Doctrine\\ORM\\EntityManagerInterface;\nuse Sabre\\HTTP\\"
  },
  {
    "path": "src/Repository/CalendarInstanceRepository.php",
    "chars": 5409,
    "preview": "<?php\n\nnamespace App\\Repository;\n\nuse App\\Entity\\Calendar;\nuse App\\Entity\\CalendarInstance;\nuse App\\Entity\\CalendarObjec"
  },
  {
    "path": "src/Repository/PrincipalRepository.php",
    "chars": 1732,
    "preview": "<?php\n\nnamespace App\\Repository;\n\nuse App\\Entity\\Principal;\nuse Doctrine\\Bundle\\DoctrineBundle\\Repository\\ServiceEntityR"
  },
  {
    "path": "src/Security/AdminUser.php",
    "chars": 1579,
    "preview": "<?php\n\nnamespace App\\Security;\n\nuse Symfony\\Component\\Security\\Core\\User\\PasswordAuthenticatedUserInterface;\nuse Symfony"
  },
  {
    "path": "src/Security/AdminUserProvider.php",
    "chars": 1871,
    "preview": "<?php\n\nnamespace App\\Security;\n\nuse Symfony\\Component\\Security\\Core\\Exception\\UnsupportedUserException;\nuse Symfony\\Comp"
  },
  {
    "path": "src/Security/ApiKeyAuthenticator.php",
    "chars": 2522,
    "preview": "<?php\n\nnamespace App\\Security;\n\nuse Symfony\\Component\\HttpFoundation\\JsonResponse;\nuse Symfony\\Component\\HttpFoundation\\"
  },
  {
    "path": "src/Security/LoginFormAuthenticator.php",
    "chars": 3275,
    "preview": "<?php\n\nnamespace App\\Security;\n\nuse Symfony\\Component\\HttpFoundation\\RedirectResponse;\nuse Symfony\\Component\\HttpFoundat"
  },
  {
    "path": "src/Services/BasicAuth.php",
    "chars": 1129,
    "preview": "<?php\n\nnamespace App\\Services;\n\nuse App\\Entity\\User;\nuse Doctrine\\Persistence\\ManagerRegistry;\nuse Sabre\\DAV\\Auth\\Backen"
  },
  {
    "path": "src/Services/BirthdayService.php",
    "chars": 12423,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\n/**\n * Largely inspired by https://github.com/nextcloud/server/blob/master/apps/dav/lib"
  },
  {
    "path": "src/Services/IMAPAuth.php",
    "chars": 4429,
    "preview": "<?php\n\nnamespace App\\Services;\n\nuse App\\Entity\\User;\nuse Doctrine\\Persistence\\ManagerRegistry;\nuse Sabre\\DAV\\Auth\\Backen"
  },
  {
    "path": "src/Services/LDAPAuth.php",
    "chars": 7145,
    "preview": "<?php\n\nnamespace App\\Services;\n\nuse App\\Entity\\User;\nuse Doctrine\\Persistence\\ManagerRegistry;\nuse Sabre\\DAV\\Auth\\Backen"
  },
  {
    "path": "src/Services/Utils.php",
    "chars": 3511,
    "preview": "<?php\n\nnamespace App\\Services;\n\nuse App\\Entity\\AddressBook;\nuse App\\Entity\\Calendar;\nuse App\\Entity\\CalendarInstance;\nus"
  },
  {
    "path": "src/Version.php",
    "chars": 83,
    "preview": "<?php\n\nnamespace App;\n\nfinal class Version\n{\n    public const VERSION = '5.4.1';\n}\n"
  },
  {
    "path": "templates/_partials/add_delegate_modal.html.twig",
    "chars": 1678,
    "preview": "<div class=\"modal fade\" id=\"addDelegateModal\" tabindex=\"-1\" role=\"dialog\" aria-hidden=\"true\">\n  <div class=\"modal-dialog"
  },
  {
    "path": "templates/_partials/back_button.html.twig",
    "chars": 98,
    "preview": "<div class=\"mb-3\"><a href=\"{{ url }}\" class=\"link-opacity-75 link-offset-2\">« {{ text }}</a></div>"
  },
  {
    "path": "templates/_partials/delegate_row.html.twig",
    "chars": 1642,
    "preview": "<div class=\"list-group-item p-3\">\n    <div class=\"d-flex w-100 justify-content-between\">\n        <h5 class=\"me-auto\">\n  "
  },
  {
    "path": "templates/_partials/delete_modal.html.twig",
    "chars": 832,
    "preview": "<div id=\"deleteModal-{{ flavour }}\" class=\"modal fade\" tabindex=\"-1\" role=\"dialog\" rel=\"deleteModal\">\n  <div class=\"moda"
  },
  {
    "path": "templates/_partials/flashes.html.twig",
    "chars": 599,
    "preview": "<div aria-live=\"polite\" aria-atomic=\"true\" class=\"flashes\">\n  <div class=\"inner\">\n    {% for label, messages in app.flas"
  },
  {
    "path": "templates/_partials/navigation.html.twig",
    "chars": 2901,
    "preview": "<nav class=\"navbar fixed-top navbar-expand-lg bg-body-tertiary\">\n  <div class=\"container\">\n    <a class=\"navbar-brand\" h"
  },
  {
    "path": "templates/_partials/share_modal.html.twig",
    "chars": 2683,
    "preview": "<div class=\"modal fade\" id=\"shareModal\" tabindex=\"-1\" role=\"dialog\" aria-hidden=\"true\">\n  <div class=\"modal-dialog\" role"
  },
  {
    "path": "templates/addressbooks/edit.html.twig",
    "chars": 584,
    "preview": "{% extends 'base.html.twig' %}\n{% set menu = 'resources' %}\n\n{% block body %}\n\n{% include '_partials/back_button.html.tw"
  },
  {
    "path": "templates/addressbooks/index.html.twig",
    "chars": 2272,
    "preview": "{% extends 'base.html.twig' %}\n{% set menu = 'resources' %}\n\n{% block body %}\n\n{% include '_partials/back_button.html.tw"
  },
  {
    "path": "templates/base.html.twig",
    "chars": 729,
    "preview": "<!DOCTYPE html>\n<html>\n    <head>\n        <meta charset=\"UTF-8\">\n        <title>{% block title %}Davis{% endblock %}</ti"
  },
  {
    "path": "templates/calendars/edit.html.twig",
    "chars": 566,
    "preview": "{% extends 'base.html.twig' %}\n{% set menu = 'resources' %}\n\n{% block body %}\n\n{% include '_partials/back_button.html.tw"
  },
  {
    "path": "templates/calendars/index.html.twig",
    "chars": 11762,
    "preview": "{% extends 'base.html.twig' %}\n{% set menu = 'resources' %}\n\n{% block body %}\n\n{% include '_partials/back_button.html.tw"
  },
  {
    "path": "templates/dashboard.html.twig",
    "chars": 4523,
    "preview": "{% extends 'base.html.twig' %}\n{% set menu = 'dashboard' %}\n\n{% block body %}\n\n<h1 class=\"display-4 fw-lighter\">{{ \"titl"
  },
  {
    "path": "templates/index.html.twig",
    "chars": 2346,
    "preview": "<!DOCTYPE html>\n<html>\n    <head>\n        <meta charset=\"UTF-8\">\n        <meta name=\"viewport\" content=\"width=device-wid"
  },
  {
    "path": "templates/mails/scheduling.html.twig",
    "chars": 5067,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n  <title>Calendar notification from {{ senderName }}</title>\n  <meta http-equiv=\"content-t"
  },
  {
    "path": "templates/mails/scheduling.txt.twig",
    "chars": 1360,
    "preview": "Calendar notification from {{ senderName }}.\n\n-----------------------------------------------------------\n\n{% if action "
  },
  {
    "path": "templates/security/login.html.twig",
    "chars": 1632,
    "preview": "{% extends 'base.html.twig' %}\n{% set menu = null %}\n\n{% block body %}\n\n{% if app.user %}\n    <div class=\"mb-3\">\n       "
  },
  {
    "path": "templates/users/delegates.html.twig",
    "chars": 1776,
    "preview": "{% extends 'base.html.twig' %}\n{% set menu = 'resources' %}\n\n{% block body %}\n\n{% include '_partials/back_button.html.tw"
  },
  {
    "path": "templates/users/edit.html.twig",
    "chars": 421,
    "preview": "{% extends 'base.html.twig' %}\n{% set menu = 'resources' %}\n\n{% block body %}\n\n{% include '_partials/back_button.html.tw"
  },
  {
    "path": "templates/users/index.html.twig",
    "chars": 3548,
    "preview": "{% extends 'base.html.twig' %}\n{% set menu = 'resources' %}\n\n{% block body %}\n\n<h1 class=\"display-4 d-flex fw-lighter ju"
  },
  {
    "path": "tests/.gitignore",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "tests/Functional/Commands/SyncBirthdayCalendarTest.php",
    "chars": 6631,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace App\\Tests\\Functional\\Command;\n\nuse App\\Constants;\nuse App\\Entity\\AddressBook;"
  },
  {
    "path": "tests/Functional/Controllers/AddressBookControllerTest.php",
    "chars": 4056,
    "preview": "<?php\n\nnamespace App\\Tests\\Functional;\n\nuse App\\Entity\\AddressBook;\nuse App\\Entity\\User;\nuse App\\Security\\AdminUser;\nuse"
  },
  {
    "path": "tests/Functional/Controllers/ApiControllerTest.php",
    "chars": 23630,
    "preview": "<?php\n\nnamespace App\\Tests\\Functional;\n\nuse Symfony\\Bundle\\FrameworkBundle\\Test\\WebTestCase;\n\nclass ApiControllerTest ex"
  },
  {
    "path": "tests/Functional/Controllers/CalendarControllerTest.php",
    "chars": 3998,
    "preview": "<?php\n\nnamespace App\\Tests\\Functional;\n\nuse App\\Entity\\User;\nuse App\\Repository\\CalendarInstanceRepository;\nuse App\\Secu"
  },
  {
    "path": "tests/Functional/Controllers/DashboardTest.php",
    "chars": 3117,
    "preview": "<?php\n\nnamespace App\\Tests\\Functional;\n\nuse Symfony\\Bundle\\FrameworkBundle\\Test\\WebTestCase;\n\nclass DashboardTest extend"
  },
  {
    "path": "tests/Functional/Controllers/UserControllerTest.php",
    "chars": 4752,
    "preview": "<?php\n\nnamespace App\\Tests\\Functional;\n\nuse App\\Entity\\User;\nuse App\\Security\\AdminUser;\nuse Symfony\\Bundle\\FrameworkBun"
  },
  {
    "path": "tests/Functional/DavTest.php",
    "chars": 914,
    "preview": "<?php\n\nnamespace App\\Tests\\Functional;\n\nuse Symfony\\Bundle\\FrameworkBundle\\Test\\WebTestCase;\nuse Symfony\\Component\\Brows"
  },
  {
    "path": "tests/Functional/Service/BirthdayServiceTest.php",
    "chars": 12888,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace App\\Tests\\Services;\n\nuse App\\Constants;\nuse App\\Entity\\AddressBook;\nuse App\\E"
  },
  {
    "path": "tests/bootstrap.php",
    "chars": 830,
    "preview": "<?php\n\nuse Symfony\\Component\\Dotenv\\Dotenv;\n\nrequire dirname(__DIR__).'/vendor/autoload.php';\n\nif (file_exists(dirname(_"
  },
  {
    "path": "translations/.gitignore",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "translations/messages+intl-icu.de.xlf",
    "chars": 27601,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<xliff xmlns=\"urn:oasis:names:tc:xliff:document:1.2\" version=\"1.2\">\n  <file sourc"
  },
  {
    "path": "translations/messages+intl-icu.en.xlf",
    "chars": 26762,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<xliff xmlns=\"urn:oasis:names:tc:xliff:document:1.2\" version=\"1.2\">\n  <file sourc"
  },
  {
    "path": "translations/messages+intl-icu.fr.xliff",
    "chars": 27564,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<xliff xmlns=\"urn:oasis:names:tc:xliff:document:1.2\" version=\"1.2\">\n  <file sourc"
  },
  {
    "path": "translations/security.de.xlf",
    "chars": 3836,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<xliff xmlns=\"urn:oasis:names:tc:xliff:document:1.2\" version=\"1.2\">\n  <file sourc"
  },
  {
    "path": "translations/security.en.xlf",
    "chars": 3684,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<xliff xmlns=\"urn:oasis:names:tc:xliff:document:1.2\" version=\"1.2\">\n  <file sourc"
  },
  {
    "path": "translations/security.fr.xlf",
    "chars": 4375,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<xliff xmlns=\"urn:oasis:names:tc:xliff:document:1.2\" version=\"1.2\">\n    <file sou"
  },
  {
    "path": "translations/validators.de.xlf",
    "chars": 27093,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<xliff xmlns=\"urn:oasis:names:tc:xliff:document:1.2\" version=\"1.2\">\n  <file sourc"
  },
  {
    "path": "translations/validators.en.xlf",
    "chars": 26412,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<xliff xmlns=\"urn:oasis:names:tc:xliff:document:1.2\" version=\"1.2\">\n  <file sourc"
  },
  {
    "path": "translations/validators.fr.xlf",
    "chars": 36029,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<xliff xmlns=\"urn:oasis:names:tc:xliff:document:1.2\" version=\"1.2\">\n    <file sou"
  }
]

About this extraction

This page contains the full source code of the tchapi/davis GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 174 files (746.3 KB), approximately 189.5k tokens, and a symbol index with 522 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!