diff --git a/.github/workflows/changelog.yaml b/.github/workflows/changelog.yaml
new file mode 100644
index 0000000..fead572
--- /dev/null
+++ b/.github/workflows/changelog.yaml
@@ -0,0 +1,29 @@
+# Do not edit this file! Make a pull request on changing
+# github/workflows/changelog.yaml in
+# https://github.com/itk-dev/devops_itkdev-docker if need be.
+
+### ### Changelog
+###
+### Checks that changelog has been updated
+
+name: Changelog
+
+on:
+ pull_request:
+
+jobs:
+ changelog:
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v5
+ with:
+ fetch-depth: 2
+
+ - name: Git fetch
+ run: git fetch
+
+ - name: Check that changelog has been updated.
+ run: git diff --exit-code origin/${{ github.base_ref }} -- CHANGELOG.md && exit 1 || exit 0
diff --git a/.github/workflows/composer.yaml b/.github/workflows/composer.yaml
new file mode 100644
index 0000000..5fb0ef0
--- /dev/null
+++ b/.github/workflows/composer.yaml
@@ -0,0 +1,85 @@
+# Do not edit this file! Make a pull request on changing
+# github/workflows/composer.yaml in
+# https://github.com/itk-dev/devops_itkdev-docker if need be.
+
+### ### Composer
+###
+### Validates composer.json and checks that it's normalized.
+###
+### #### Assumptions
+###
+### 1. A docker compose service named `phpfpm` can be run and `composer` can be
+### run inside the `phpfpm` service.
+### 2. [ergebnis/composer-normalize](https://github.com/ergebnis/composer-normalize)
+### is a dev requirement in `composer.json`:
+###
+### ``` shell
+### docker compose run --rm phpfpm composer require --dev ergebnis/composer-normalize
+### ```
+###
+### Normalize `composer.json` by running
+###
+### ``` shell
+### docker compose run --rm phpfpm composer normalize
+### ```
+
+name: Composer
+
+env:
+ COMPOSE_USER: root
+
+on:
+ pull_request:
+ push:
+ branches:
+ - main
+ - develop
+
+jobs:
+ composer-validate:
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ steps:
+ - uses: actions/checkout@v5
+
+ - name: Create docker network
+ run: |
+ docker network create frontend
+
+ - run: |
+ docker compose run --rm phpfpm composer validate --strict
+
+ composer-normalized:
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ steps:
+ - uses: actions/checkout@v5
+
+ - name: Create docker network
+ run: |
+ docker network create frontend
+
+ - run: |
+ docker compose run --rm --env COMPOSER=composer.lenient.json phpfpm composer init --no-interaction
+ docker compose run --rm --env COMPOSER=composer.lenient.json phpfpm composer config --no-plugins allow-plugins.mglaman/composer-drupal-lenient true
+ docker compose run --rm --env COMPOSER=composer.lenient.json phpfpm composer require mglaman/composer-drupal-lenient
+ docker compose run --rm --env COMPOSER=composer.lenient.json phpfpm rm composer.lenient.*
+
+ docker compose run --rm phpfpm composer install
+ docker compose run --rm phpfpm composer normalize --dry-run
+
+ composer-audit:
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ steps:
+ - uses: actions/checkout@v5
+
+ - name: Create docker network
+ run: |
+ docker network create frontend
+
+ - run: |
+ docker compose run --rm phpfpm composer audit
diff --git a/.github/workflows/markdown.yaml b/.github/workflows/markdown.yaml
new file mode 100644
index 0000000..ae83163
--- /dev/null
+++ b/.github/workflows/markdown.yaml
@@ -0,0 +1,44 @@
+# Do not edit this file! Make a pull request on changing
+# github/workflows/markdown.yaml in
+# https://github.com/itk-dev/devops_itkdev-docker if need be.
+
+### ### Markdown
+###
+### Lints Markdown files (`**/*.md`) in the project.
+###
+### [markdownlint-cli configuration
+### files](https://github.com/igorshubovych/markdownlint-cli?tab=readme-ov-file#configuration),
+### `.markdownlint.jsonc` and `.markdownlintignore`, control what is actually
+### linted and how.
+###
+### #### Assumptions
+###
+### 1. A docker compose service named `markdownlint` for running `markdownlint`
+### (from
+### [markdownlint-cli](https://github.com/igorshubovych/markdownlint-cli))
+### exists.
+
+name: Markdown
+
+on:
+ pull_request:
+ push:
+ branches:
+ - main
+ - develop
+
+jobs:
+ markdown-lint:
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v5
+
+ - name: Create docker network
+ run: |
+ docker network create frontend
+
+ - run: |
+ docker compose run --rm markdownlint markdownlint '**/*.md'
diff --git a/.github/workflows/php.yaml b/.github/workflows/php.yaml
new file mode 100644
index 0000000..3b6415d
--- /dev/null
+++ b/.github/workflows/php.yaml
@@ -0,0 +1,64 @@
+# Do not edit this file! Make a pull request on changing
+# github/workflows/drupal-module/php.yaml in
+# https://github.com/itk-dev/devops_itkdev-docker if need be.
+
+### ### Drupal module PHP
+###
+### Checks that PHP code adheres to the [Drupal coding
+### standards](https://www.drupal.org/docs/develop/standards).
+###
+### #### Assumptions
+###
+### 1. A docker compose service named `phpfpm` can be run and `composer` can be
+### run inside the `phpfpm` service.
+### 2. [drupal/coder](https://www.drupal.org/project/coder) is a dev requirement
+### in `composer.json`:
+###
+### ``` shell
+### docker compose run --rm phpfpm composer require --dev drupal/coder
+### ```
+###
+### Clean up and check code by running
+###
+### ``` shell
+### docker compose run --rm phpfpm vendor/bin/phpcbf
+### docker compose run --rm phpfpm vendor/bin/phpcs
+### ```
+###
+### > [!NOTE]
+### > The template adds `.phpcs.xml.dist` as [a configuration file for
+### > PHP_CodeSniffer](https://github.com/squizlabs/PHP_CodeSniffer/wiki/Advanced-Usage#using-a-default-configuration-file)
+### > and this makes it possible to override the actual configuration used in a
+### > project by adding a more important configuration file, e.g. `.phpcs.xml`.
+
+name: PHP
+
+env:
+ COMPOSE_USER: root
+
+on:
+ pull_request:
+ push:
+ branches:
+ - main
+ - develop
+
+jobs:
+ coding-standards:
+ name: PHP - Check Coding Standards
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v5
+
+ - name: Create docker network
+ run: |
+ docker network create frontend
+
+ - run: |
+ docker compose run --rm --env COMPOSER=composer.lenient.json phpfpm composer init --no-interaction
+ docker compose run --rm --env COMPOSER=composer.lenient.json phpfpm composer config --no-plugins allow-plugins.mglaman/composer-drupal-lenient true
+ docker compose run --rm --env COMPOSER=composer.lenient.json phpfpm composer require mglaman/composer-drupal-lenient
+ docker compose run --rm --env COMPOSER=composer.lenient.json phpfpm rm composer.lenient.*
+
+ docker compose run --rm phpfpm composer install
+ docker compose run --rm phpfpm vendor/bin/phpcs
diff --git a/.github/workflows/project.yaml b/.github/workflows/project.yaml
new file mode 100644
index 0000000..da5ae3d
--- /dev/null
+++ b/.github/workflows/project.yaml
@@ -0,0 +1,30 @@
+name: Project
+
+env:
+ COMPOSE_USER: root
+
+on:
+ pull_request:
+ push:
+ branches:
+ - main
+ - develop
+
+jobs:
+ code-analysis:
+ name: PHP - Code analysis
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v5
+
+ - run: |
+ ./scripts/code-analysis
+
+ rector:
+ name: PHP - Rector
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v5
+
+ - run: |
+ ./scripts/rector
diff --git a/.github/workflows/yaml.yaml b/.github/workflows/yaml.yaml
new file mode 100644
index 0000000..631e525
--- /dev/null
+++ b/.github/workflows/yaml.yaml
@@ -0,0 +1,41 @@
+# Do not edit this file! Make a pull request on changing
+# github/workflows/yaml.yaml in
+# https://github.com/itk-dev/devops_itkdev-docker if need be.
+
+### ### YAML
+###
+### Validates YAML files.
+###
+### #### Assumptions
+###
+### 1. A docker compose service named `prettier` for running
+### [Prettier](https://prettier.io/) exists.
+###
+### #### Symfony YAML
+###
+### Symfony's YAML config files use 4 spaces for indentation and single quotes.
+### Therefore we use a [Prettier configuration
+### file](https://prettier.io/docs/configuration), `.prettierrc.yaml`, to make
+### Prettier format YAML files in the `config/` folder like Symfony expects.
+
+name: YAML
+
+on:
+ pull_request:
+ push:
+ branches:
+ - main
+ - develop
+
+jobs:
+ yaml-lint:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v5
+
+ - name: Create docker network
+ run: |
+ docker network create frontend
+
+ - run: |
+ docker compose run --rm prettier '**/*.{yml,yaml}' --check
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..d8a7996
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+composer.lock
+vendor/
diff --git a/.markdownlint.jsonc b/.markdownlint.jsonc
new file mode 100644
index 0000000..0253096
--- /dev/null
+++ b/.markdownlint.jsonc
@@ -0,0 +1,22 @@
+// This file is copied from config/markdown/.markdownlint.jsonc in https://github.com/itk-dev/devops_itkdev-docker.
+// Feel free to edit the file, but consider making a pull request if you find a general issue with the file.
+
+// markdownlint-cli configuration file (cf. https://github.com/igorshubovych/markdownlint-cli?tab=readme-ov-file#configuration)
+{
+ "default": true,
+ // https://github.com/DavidAnson/markdownlint/blob/main/doc/md013.md
+ "line-length": {
+ "line_length": 120,
+ "code_blocks": false,
+ "tables": false
+ },
+ // https://github.com/DavidAnson/markdownlint/blob/main/doc/md024.md
+ "no-duplicate-heading": {
+ "siblings_only": true
+ },
+ // https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/organizing-information-with-collapsed-sections#creating-a-collapsed-section
+ // https://github.com/DavidAnson/markdownlint/blob/main/doc/md033.md
+ "no-inline-html": {
+ "allowed_elements": ["details", "summary"]
+ }
+}
diff --git a/.markdownlintignore b/.markdownlintignore
new file mode 100644
index 0000000..d143ace
--- /dev/null
+++ b/.markdownlintignore
@@ -0,0 +1,12 @@
+# This file is copied from config/markdown/.markdownlintignore in https://github.com/itk-dev/devops_itkdev-docker.
+# Feel free to edit the file, but consider making a pull request if you find a general issue with the file.
+
+# https://github.com/igorshubovych/markdownlint-cli?tab=readme-ov-file#ignoring-files
+vendor/
+node_modules/
+LICENSE.md
+# Drupal
+web/*.md
+web/core/
+web/libraries/
+web/*/contrib/
diff --git a/.phpcs.xml.dist b/.phpcs.xml.dist
new file mode 100644
index 0000000..083aa41
--- /dev/null
+++ b/.phpcs.xml.dist
@@ -0,0 +1,33 @@
+
+
+
+
+
+ The coding standard.
+
+ .
+
+
+ vendor
+ rector.php
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ src/Settings/
+
+
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..b337319
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,10 @@
+# Changelog
+
+All notable changes to this project will be documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
+and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+
+## [Unreleased]
+
+[Unreleased]: https://github.com/itk-dev/os2forms_f2
diff --git a/Taskfile.yml b/Taskfile.yml
new file mode 100644
index 0000000..b4d390e
--- /dev/null
+++ b/Taskfile.yml
@@ -0,0 +1,118 @@
+# https://taskfile.dev
+
+version: "3"
+
+tasks:
+ compose:
+ cmds:
+ - docker compose {{.TASK_ARGS}} {{.CLI_ARGS}}
+ internal: true
+
+ composer:
+ desc: Run composer inside docker compose setup, e.g. `task {{.TASK}} -- install`
+ cmds:
+ - task: compose
+ vars:
+ TASK_ARGS: run --rm phpfpm composer {{.TASK_ARGS}}
+
+ composer:install:
+ desc: Run composer inside docker compose setup, e.g. `task {{.TASK}} -- install`
+ cmds:
+ - rm -fr composer.lock vendor
+ - |
+ # Create a temporary composer file to install https://github.com/mglaman/composer-drupal-lenient before the real install needs it.
+ docker compose run --rm --env COMPOSER=composer.lenient.json phpfpm composer init --no-interaction
+ docker compose run --rm --env COMPOSER=composer.lenient.json phpfpm composer config --no-plugins allow-plugins.mglaman/composer-drupal-lenient true
+ docker compose run --rm --env COMPOSER=composer.lenient.json phpfpm composer require mglaman/composer-drupal-lenient
+ docker compose run --rm --env COMPOSER=composer.lenient.json phpfpm rm composer.lenient.*
+
+ - task: composer
+ vars:
+ TASK_ARGS: install
+
+ coding-standards:apply:
+ desc: "Apply coding standards"
+ cmds:
+ - task: coding-standards:composer:apply
+ - task: coding-standards:markdown:apply
+ - task: coding-standards:php:apply
+ - task: coding-standards:yaml:apply
+ silent: true
+
+ coding-standards:check:
+ desc: "Check coding standards"
+ cmds:
+ - task: coding-standards:composer:check
+ - task: coding-standards:markdown:check
+ - task: coding-standards:php:check
+ - task: coding-standards:yaml:check
+ silent: true
+
+ coding-standards:composer:apply:
+ desc: "Apply coding standards for composer"
+ cmds:
+ - task: compose
+ vars:
+ TASK_ARGS: run --rm phpfpm composer normalize
+
+ coding-standards:composer:check:
+ - task: coding-standards:composer:apply
+ - task: compose
+ vars:
+ TASK_ARGS: run --rm phpfpm composer normalize --dry-run
+ - task: compose
+ vars:
+ TASK_ARGS: run --rm phpfpm composer validate
+
+ coding-standards:markdown:apply:
+ desc: "Apply coding standards for Markdown"
+ cmds:
+ # Cf. .github/workflows/markdown.yaml
+ - docker compose run --rm markdownlint markdownlint '**/*.md' --fix
+
+ coding-standards:markdown:check:
+ desc: "Apply and check coding standards for Markdown"
+ cmds:
+ - task: coding-standards:markdown:apply
+ # Cf. .github/workflows/markdown.yaml
+ - docker compose run --rm markdownlint markdownlint '**/*.md'
+
+ coding-standards:php:apply:
+ desc: "Apply coding standards for PHP"
+ cmds:
+ # Cf. .github/workflows/php.yaml
+ - docker compose run --rm phpfpm vendor/bin/phpcbf
+ silent: true
+
+ coding-standards:php:check:
+ desc: "Apply and check coding standards for PHP"
+ cmds:
+ - task: coding-standards:php:apply
+ # Cf. .github/workflows/php.yaml
+ - docker compose run --rm phpfpm vendor/bin/phpcs
+ silent: true
+
+ coding-standards:yaml:apply:
+ desc: "Apply coding standards for YAML"
+ cmds:
+ # Cf. .github/workflows/yaml.yaml
+ - docker compose run --rm prettier '**/*.{yml,yaml}' --write
+
+ coding-standards:yaml:check:
+ desc: "Apply coding standards for YAML"
+ cmds:
+ - task: coding-standards:yaml:apply
+ # Cf. .github/workflows/yaml.yaml
+ - docker compose run --rm prettier '**/*.{yml,yaml}' --check
+
+ test:
+ cmds:
+ - docker compose run --env PHP_XDEBUG_MODE --env PHP_XDEBUG_WITH_REQUEST
+ --env PHP_IDE_CONFIG --rm phpfpm vendor/bin/phpunit {{.CLI_ARGS}}
+
+ xdebug:test:
+ cmds:
+ - PHP_XDEBUG_MODE=debug PHP_XDEBUG_WITH_REQUEST=yes
+ PHP_IDE_CONFIG=serverName=localhost docker compose run --env
+ PHP_XDEBUG_MODE --env PHP_XDEBUG_WITH_REQUEST --env PHP_IDE_CONFIG --rm
+ phpfpm vendor/bin/phpunit {{.CLI_ARGS}}
diff --git a/compose.yaml b/compose.yaml
new file mode 100644
index 0000000..c4f59d0
--- /dev/null
+++ b/compose.yaml
@@ -0,0 +1,32 @@
+services:
+ phpfpm:
+ image: itkdev/php8.3-fpm:latest
+ user: ${COMPOSE_USER:-deploy}
+ profiles:
+ - dev
+ volumes:
+ - .:/app-os2forms_f2
+ working_dir: /app-os2forms_f2
+ environment:
+ # https://getcomposer.org/doc/03-cli.md#composer-no-security-blocking
+ # @see https://github.com/OS2Forms/os2forms/issues/245
+ COMPOSER_NO_SECURITY_BLOCKING: 1
+
+ # Code checks tools
+ markdownlint:
+ image: itkdev/markdownlint
+ profiles:
+ - dev
+ volumes:
+ - ./:/md
+
+ prettier:
+ # Prettier does not (yet, fcf.
+ # https://github.com/prettier/prettier/issues/15206) have an official
+ # docker image.
+ # https://hub.docker.com/r/jauderho/prettier is good candidate (cf. https://hub.docker.com/search?q=prettier&sort=updated_at&order=desc)
+ image: jauderho/prettier
+ profiles:
+ - dev
+ volumes:
+ - ./:/work
diff --git a/composer.json b/composer.json
new file mode 100644
index 0000000..8c574c2
--- /dev/null
+++ b/composer.json
@@ -0,0 +1,31 @@
+{
+ "name": "os2forms/os2forms_f2",
+ "description": "OS2Forms: F2 integration",
+ "license": "MIT",
+ "type": "drupal-module",
+ "require": {
+ "itk-dev/f2-api-client": "dev-f2-api-client as 1.0.0",
+ "os2forms/os2forms": "^5.0"
+ },
+ "require-dev": {
+ "drupal/coder": "^9.0",
+ "ergebnis/composer-normalize": "^2.52"
+ },
+ "repositories": [
+ {
+ "type": "composer",
+ "url": "https://packages.drupal.org/8"
+ }
+ ],
+ "minimum-stability": "dev",
+ "config": {
+ "allow-plugins": {
+ "cweagans/composer-patches": true,
+ "dealerdirect/phpcodesniffer-composer-installer": true,
+ "ergebnis/composer-normalize": true,
+ "mglaman/composer-drupal-lenient": true,
+ "simplesamlphp/composer-module-installer": true,
+ "zaporylie/composer-drupal-optimizations": true
+ }
+ }
+}
diff --git a/os2forms_f2.info.yml b/os2forms_f2.info.yml
new file mode 100644
index 0000000..3e5f5ee
--- /dev/null
+++ b/os2forms_f2.info.yml
@@ -0,0 +1,7 @@
+name: "os2forms_f2"
+type: module
+description: "OS2Forms: F2 integration"
+package: OS2Forms
+core_version_requirement: ^10 || ^11
+dependencies:
+ - os2forms:os2forms
diff --git a/phpstan.neon b/phpstan.neon
new file mode 100644
index 0000000..716b6c6
--- /dev/null
+++ b/phpstan.neon
@@ -0,0 +1,14 @@
+parameters:
+ paths:
+ - .
+ level: 5
+ customRulesetUsed: true
+ reportUnmatchedIgnoredErrors: false
+ excludePaths:
+ - rector.php
+ # Ignore any vendor folder (https://phpstan.org/user-guide/ignoring-errors#excluding-whole-files)
+ - vendor (?)
+
+ ignoreErrors:
+ - '#Call to method Drupal\\Core\\Entity\\Query\\QueryInterface::accessCheck\(\) will always evaluate to true.#'
+ - '#Call to method PHPUnit\\Framework\\Assert::assertTrue\(\) with true will always evaluate to true.#'
diff --git a/rector.php b/rector.php
new file mode 100644
index 0000000..44fe530
--- /dev/null
+++ b/rector.php
@@ -0,0 +1,21 @@
+withPaths([
+ __DIR__ . '/src',
+ // __DIR__ . '/tests',
+ ])
+ ->withSets([
+ Drupal10SetList::DRUPAL_10,
+ ])
+ ->withPhpSets(php83: TRUE)
+ ->withTypeCoverageLevel(0);
diff --git a/scripts/.env b/scripts/.env
new file mode 100644
index 0000000..da1e572
--- /dev/null
+++ b/scripts/.env
@@ -0,0 +1,2 @@
+COMPOSE_PROJECT_NAME=drupal-module
+MODULE_NAME=os2forms_f2
diff --git a/scripts/base b/scripts/base
new file mode 100644
index 0000000..def2c9d
--- /dev/null
+++ b/scripts/base
@@ -0,0 +1,138 @@
+#!/usr/bin/env bash
+set -o errexit -o errtrace -o noclobber -o nounset -o pipefail
+IFS=$'\n\t'
+
+execute_name=execute
+
+usage() {
+ (cat >&2 </dev/null); then
+ (cat >&2 <&2 <