diff --git a/.ddev/.env.web b/.ddev/.env.web new file mode 100644 index 0000000..2d858f6 --- /dev/null +++ b/.ddev/.env.web @@ -0,0 +1 @@ +DRUPAL_CORE="^10.5.10" diff --git a/.ddev/addon-metadata/ddev-drupal-contrib/manifest.yaml b/.ddev/addon-metadata/ddev-drupal-contrib/manifest.yaml new file mode 100644 index 0000000..05df9d8 --- /dev/null +++ b/.ddev/addon-metadata/ddev-drupal-contrib/manifest.yaml @@ -0,0 +1,19 @@ +name: ddev-drupal-contrib +repository: ddev/ddev-drupal-contrib +version: 1.1.7 +install_date: "2026-08-25T10:26:26+02:00" +project_files: + - commands/host/core-version + - commands/web/eslint + - commands/web/expand-composer-json + - commands/web/nightwatch + - commands/web/phpcbf + - commands/web/phpcs + - commands/web/phpstan + - commands/web/phpunit + - commands/web/poser + - commands/web/stylelint + - commands/web/symlink-project + - config.contrib.yaml +global_files: [] +removal_actions: [] diff --git a/.ddev/commands/host/core-version b/.ddev/commands/host/core-version new file mode 100755 index 0000000..e3e0122 --- /dev/null +++ b/.ddev/commands/host/core-version @@ -0,0 +1,22 @@ +#!/usr/bin/env bash + +#ddev-generated +## Command provided by https://github.com/ddev/ddev-drupal-contrib +## Description: Switch the core version and rebuild. +## Usage: core-version [version] +## Example: "ddev core-version ^11" or "ddev core-version ~11.1.0" + +set -eu -o pipefail + +# Handle default values. +DRUPAL_CORE=${1:-default} +if [ "$DRUPAL_CORE" == "default" ]; then + DRUPAL_CORE="" +fi + +# Set/clear the env. +ddev dotenv set .ddev/.env.web --drupal-core "${DRUPAL_CORE}" + +# Restart and rebuild. +ddev restart +ddev poser diff --git a/.ddev/config.contrib.yaml b/.ddev/config.contrib.yaml new file mode 100644 index 0000000..344b786 --- /dev/null +++ b/.ddev/config.contrib.yaml @@ -0,0 +1,24 @@ +#ddev-generated +## Command provided by https://github.com/ddev/ddev-drupal-contrib +## To customize this configuration, see: +## https://ddev.readthedocs.io/en/stable/users/extend/customization-extendibility/ +web_environment: + # To change the Drupal core version, see the README: + # https://github.com/ddev/ddev-drupal-contrib/blob/main/README.md#changing-the-drupal-core-version + # https://git.drupalcode.org/project/gitlab_templates/-/blob/1.9.6/scripts/expand_composer_json.php?ref_type=tags#L15 + - IGNORE_PROJECT_DRUPAL_CORE_VERSION=1 + # To change the location of your project code, see the README: + # https://github.com/ddev/ddev-drupal-contrib/blob/main/README.md#changing-the-symlink-location + - DRUPAL_PROJECTS_PATH=modules/custom + - SIMPLETEST_DB=mysql://db:db@db/db + - SIMPLETEST_BASE_URL=http://web + - BROWSERTEST_OUTPUT_DIRECTORY=/tmp + - BROWSERTEST_OUTPUT_BASE_URL=${DDEV_PRIMARY_URL} +hooks: + post-start: + - exec-host: | + if [[ -f vendor/autoload.php ]]; then + ddev symlink-project + else + exit 0 + fi diff --git a/.ddev/config.yaml b/.ddev/config.yaml new file mode 100644 index 0000000..03b123d --- /dev/null +++ b/.ddev/config.yaml @@ -0,0 +1,296 @@ +name: os2forms-f2 +type: drupal10 +docroot: web +php_version: "8.3" +webserver_type: nginx-fpm +xdebug_enabled: false +additional_hostnames: [] +additional_fqdns: [] +database: + type: mariadb + version: "11.8" +use_dns_when_possible: true +composer_version: "2" +web_environment: [] +nodejs_version: "24" +corepack_enable: false + +# Key features of DDEV's config.yaml: + +# name: # Name of the project, automatically provides +# http://projectname.ddev.site and https://projectname.ddev.site +# If the name is omitted, the project will take the name of the enclosing directory, +# which is useful if you want to have a copy of the project side by side with this one. + +# type: # asterios, backdrop, cakephp, codeigniter, craftcms, drupal, drupal6, drupal7, drupal8, drupal9, drupal10, drupal11, drupal12, generic, joomla, laravel, magento, magento2, php, shopware6, silverstripe, symfony, typo3, wordpress, wp-bedrock +# See https://docs.ddev.com/en/stable/users/quickstart/ for more +# information on the different project types + +# docroot: # Relative path to the directory containing index.php. + +# php_version: "8.4" # PHP version to use, "5.6" through "8.5" + +# You can explicitly specify the webimage but this +# is not recommended, as the images are often closely tied to DDEV's behavior, +# so this can break upgrades. + +# webimage: +# It’s unusual to change this option, and we don’t recommend it without Docker experience and a good reason. +# Typically, this means additions to the existing web image using a .ddev/web-build/Dockerfile.* + +# database: +# type: # mysql, mariadb, postgres +# version: # database version, like "10.11" or "8.0" +# MariaDB versions can be 5.5-10.8, 10.11, 11.4, 11.8, 12.3 +# MySQL versions can be 5.5-8.0, 8.4 +# PostgreSQL versions can be 9-18 + +# router_http_port: # Port to be used for http (defaults to global configuration, usually 80) +# router_https_port: # Port for https (defaults to global configuration, usually 443) + +# xdebug_enabled: false # Set to true to enable Xdebug and "ddev start" or "ddev restart" +# Note that for most people the commands +# "ddev xdebug" to enable Xdebug and "ddev xdebug off" to disable it work better, +# as leaving Xdebug enabled all the time is a big performance hit. + +# xhgui_http_port: "8143" +# xhgui_https_port: "8142" +# The XHGui ports can be changed from the default 8143 and 8142 +# Very rarely used + +# host_xhgui_port: "8142" +# Can be used to change the host binding port of the XHGui +# application. Rarely used; only when port conflict and +# bind_all_ports is used (normally with router disabled) + +# xhprof_mode: [prepend|xhgui|global] +# Default is "xhgui" + +# webserver_type: nginx-fpm, apache-fpm, generic + +# timezone: Europe/Berlin +# If timezone is unset, DDEV will attempt to derive it from the host system timezone +# using the $TZ environment variable or the /etc/localtime symlink. +# This is the timezone used in the containers and by PHP; +# it can be set to any valid timezone, +# see https://en.wikipedia.org/wiki/List_of_tz_database_time_zones +# For example Europe/Dublin or MST7MDT + +# composer_root: +# Relative path to the Composer root directory from the project root. This is +# the directory which contains the composer.json and where all Composer related +# commands are executed. + +# composer_version: "2" +# You can set it to "" or "2" (default) for Composer v2 +# to use the latest major version available at the time your container is built. +# It is also possible to use any other Composer version channel. This includes: +# - 2.2 (latest Composer LTS version) +# - stable +# - preview +# - snapshot +# Alternatively, an explicit Composer version may be specified, for example "2.2.18". +# To reinstall Composer after the image was built, run "ddev utility rebuild". + +# nodejs_version: "24" +# change from the default system Node.js version to any other version. +# See https://docs.ddev.com/en/stable/users/configuration/config/#nodejs_version for more information +# and https://www.npmjs.com/package/n#specifying-nodejs-versions for the full documentation. + +# corepack_enable: false +# Change to 'true' to 'corepack enable' and gain access to latest versions of yarn/pnpm + +# additional_hostnames: +# - somename +# - someothername +# would provide http and https URLs for "somename.ddev.site" +# and "someothername.ddev.site". + +# additional_fqdns: +# - example.com +# - sub1.example.com +# would provide http and https URLs for "example.com" and "sub1.example.com" +# Please take care with this because it can cause great confusion. + +# upload_dirs: "custom/upload/dir" +# +# upload_dirs: +# - custom/upload/dir +# - ../private +# +# would set the destination paths for ddev import-files to /custom/upload/dir +# When Mutagen is enabled this path is bind-mounted so that all the files +# in the upload_dirs don't have to be synced into Mutagen. + +# disable_upload_dirs_warning: false +# If true, turns off the normal warning that says +# "You have Mutagen enabled and your 'php' project type doesn't have upload_dirs set" + +# ddev_version_constraint: "" +# Example: +# ddev_version_constraint: ">= 1.24.8" +# This will enforce that the running ddev version is within this constraint. +# See https://github.com/Masterminds/semver#checking-version-constraints for +# supported constraint formats + +# working_dir: +# web: /var/www/html +# db: /home +# would set the default working directory for the web and db services. +# These values specify the destination directory for ddev ssh and the +# directory in which commands passed into ddev exec are run. + +# omit_containers: [db, ddev-ssh-agent] +# Currently only these containers are supported. Some containers can also be +# omitted globally in the ~/.ddev/global_config.yaml. Note that if you omit +# the "db" container, several standard features of DDEV that access the +# database container will be unusable. In the global configuration it is also +# possible to omit ddev-router, but not here. + +# performance_mode: "global" +# DDEV offers performance optimization strategies to improve the filesystem +# performance depending on your host system. Should be configured globally. +# +# If set, will override the global config. Possible values are: +# - "global": uses the value from the global config. +# - "none": disables performance optimization for this project. +# - "mutagen": enables Mutagen for this project. +# +# See https://docs.ddev.com/en/stable/users/install/performance/#mutagen + +# fail_on_hook_fail: False +# Decide whether 'ddev start' should be interrupted by a failing hook + +# host_https_port: "59002" +# The host port binding for https can be explicitly specified. It is +# dynamic unless otherwise specified. +# This is not used by most people, most people use the *router* instead +# of the localhost port. + +# host_webserver_port: "59001" +# The host port binding for the ddev-webserver can be explicitly specified. It is +# dynamic unless otherwise specified. +# This is not used by most people, most people use the *router* instead +# of the localhost port. + +# host_db_port: "59002" +# The host port binding for the ddev-dbserver can be explicitly specified. It is dynamic +# unless explicitly specified. + +# mailpit_http_port: "8025" +# mailpit_https_port: "8026" +# The Mailpit ports can be changed from the default 8025 and 8026 + +# host_mailpit_port: "8025" +# The mailpit port is not normally bound on the host at all, instead being routed +# through ddev-router, but it can be bound directly to localhost if specified here. + +# webimage_extra_packages: ['php${DDEV_PHP_VERSION}-tidy', 'php${DDEV_PHP_VERSION}-yac'] +# Extra Debian packages that are needed in the webimage can be added here + +# dbimage_extra_packages: [netcat, telnet, sudo] +# Extra Debian packages that are needed in the dbimage can be added here + +# use_dns_when_possible: true +# If the host has internet access and the domain configured can +# successfully be looked up, DNS will be used for hostname resolution +# instead of editing /etc/hosts +# Defaults to true + +# project_tld: ddev.site +# The top-level domain used for project URLs +# The default "ddev.site" allows DNS lookup via a wildcard + +# share_default_provider: ngrok +# The default share provider to use for "ddev share" +# Defaults to global configuration, usually "ngrok" +# Can be "ngrok" or "cloudflared" or the name of a custom provider from .ddev/share-providers/ + +# share_provider_args: --basic-auth username:pass1234 +# Provide extra flags to the share provider script +# See https://docs.ddev.com/en/stable/users/configuration/config/#share_provider_args + +# disable_settings_management: false +# If true, DDEV will not create CMS-specific settings files like +# Drupal's settings.php/settings.ddev.php or TYPO3's additional.php +# In this case the user must provide all such settings. + +# You can inject environment variables into the web container with: +# web_environment: +# - SOMEENV=somevalue +# - SOMEOTHERENV=someothervalue + +# no_project_mount: false +# (Experimental) If true, DDEV will not mount the project into the web container; +# the user is responsible for mounting it manually or via a script. +# This is to enable experimentation with alternate file mounting strategies. +# For advanced users only! + +# bind_all_interfaces: false +# If true, host ports will be bound on all network interfaces, +# not the localhost interface only. This means that ports +# will be available on the local network if the host firewall +# allows it. + +# default_container_timeout: 120 +# The default time that DDEV waits for all containers to become ready can be increased from +# the default 120. This helps in importing huge databases, for example. + +#web_extra_exposed_ports: +#- name: nodejs +# container_port: 3000 +# http_port: 2999 +# https_port: 3000 +#- name: something +# container_port: 4000 +# https_port: 4000 +# http_port: 3999 +# Allows a set of extra ports to be exposed via ddev-router +# Fill in all three fields even if you don’t intend to use the https_port! +# If you don’t add https_port, then it defaults to 0 and ddev-router will fail to start. +# +# The port behavior on the ddev-webserver must be arranged separately, for example +# using web_extra_daemons. +# For example, with a web app on port 3000 inside the container, this config would +# expose that web app on https://.ddev.site:9999 and http://.ddev.site:9998 +# web_extra_exposed_ports: +# - name: myapp +# container_port: 3000 +# http_port: 9998 +# https_port: 9999 + +#web_extra_daemons: +#- name: "http-1" +# command: "/var/www/html/node_modules/.bin/http-server -p 3000" +# directory: /var/www/html +#- name: "http-2" +# command: "/var/www/html/node_modules/.bin/http-server /var/www/html/sub -p 3000" +# directory: /var/www/html + +# override_config: false +# By default, config.*.yaml files are *merged* into the configuration +# But this means that some things can't be overridden +# For example, if you have 'use_dns_when_possible: true' you can't override it with a merge +# and you can't erase existing hooks or all environment variables. +# However, with "override_config: true" in a particular config.*.yaml file, +# 'use_dns_when_possible: false' can override the existing values, and +# hooks: +# post-start: [] +# or +# web_environment: [] +# or +# additional_hostnames: [] +# can have their intended effect. 'override_config' affects only behavior of the +# config.*.yaml file it exists in. + +# Many DDEV commands can be extended to run tasks before or after the +# DDEV command is executed, for example "post-start", "post-import-db", +# "pre-composer", "post-composer" +# See https://docs.ddev.com/en/stable/users/extend/custom-commands/ for more +# information on the commands that can be extended and the tasks you can define +# for them. Example: +#hooks: +# post-import-db: +# - exec: drush sql:sanitize +# - exec: drush updatedb +# - exec: drush cache:rebuild diff --git a/.github/workflows/composer.yaml b/.github/workflows/composer.yaml deleted file mode 100644 index 2dd03fd..0000000 --- a/.github/workflows/composer.yaml +++ /dev/null @@ -1,83 +0,0 @@ -# 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 - - uses: go-task/setup-task@v1 - - - name: Create docker network - run: | - docker network create frontend - - - run: | - task composer:install - - - run: | - 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 --locked diff --git a/.github/workflows/php.yaml b/.github/workflows/php.yaml deleted file mode 100644 index a15e2d4..0000000 --- a/.github/workflows/php.yaml +++ /dev/null @@ -1,62 +0,0 @@ -# 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 - - uses: go-task/setup-task@v1 - - - name: Create docker network - run: | - docker network create frontend - - - run: | - task composer:install - - - run: | - docker compose run --rm phpfpm vendor/bin/phpcs diff --git a/.github/workflows/project.yaml b/.github/workflows/project.yaml index da5ae3d..04730fb 100644 --- a/.github/workflows/project.yaml +++ b/.github/workflows/project.yaml @@ -1,7 +1,7 @@ name: Project env: - COMPOSE_USER: root + COMPOSE_USER: runner on: pull_request: @@ -11,20 +11,31 @@ on: - develop jobs: - code-analysis: - name: PHP - Code analysis + php: + name: PHP runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 + - uses: go-task/setup-task@v1 + # https://github.com/ddev/github-action-setup-ddev + - uses: ddev/github-action-setup-ddev@v1 + with: + autostart: false - - run: | - ./scripts/code-analysis + - name: Start DDEV + run: task ddev:start - rector: - name: PHP - Rector - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v5 + - name: Composer - validate + run: ddev composer validate --strict + + - name: Composer - normalize + run: ddev composer normalize --dry-run + + - name: PHP coding standards + run: ddev phpcs + + - name: Code analysis + run: task code-analysis:phpstan - - run: | - ./scripts/rector + - name: Rector + run: task rector -- --dry-run diff --git a/.gitignore b/.gitignore index d8a7996..457448d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,6 @@ composer.lock vendor/ + +# DDEV stuff +web/ +recipes/ diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..3a4f5cc --- /dev/null +++ b/.prettierignore @@ -0,0 +1 @@ +.ddev/ diff --git a/CHANGELOG.md b/CHANGELOG.md index b337319..35dd95a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,4 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +* [PR-3](https://github.com/itk-dev/os2forms_f2/pull/3) + Added DDEV based development setup + [Unreleased]: https://github.com/itk-dev/os2forms_f2 diff --git a/README.md b/README.md index 999451d..12df4f8 100644 --- a/README.md +++ b/README.md @@ -8,3 +8,22 @@ 2. Go to `/admin/os2forms_f2/settings` and define settings. 3. Add a "F2" handler to a webform. + +## Development + +We use [DDEV Drupal Contrib](https://github.com/ddev/ddev-drupal-contrib) for development[^1]. + +[^1]: The DDEV environment has been created by running + + ```shell + ddev config --project-type=drupal10 --docroot=web + ddev dotenv set .ddev/.env.web --drupal-core '^10.5.10' + ``` + +Start the show by running + +```shell +task ddev:start +``` + +Run `task` to see other useful development tasks. diff --git a/Taskfile.yml b/Taskfile.yml index a0e046d..425c2dd 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -3,33 +3,12 @@ version: "3" tasks: - compose: + ddev: + desc: "Run DDEV command" cmds: - - docker compose {{.TASK_ARGS}} {{.CLI_ARGS}} + - ddev {{.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:^1.0 - 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: @@ -37,7 +16,6 @@ tasks: - task: coding-standards:markdown:apply - task: coding-standards:php:apply - task: coding-standards:yaml:apply - silent: true coding-standards:check: desc: "Check coding standards" @@ -46,23 +24,22 @@ tasks: - 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 + - task: ddev vars: - TASK_ARGS: run --rm phpfpm composer normalize + TASK_ARGS: composer normalize coding-standards:composer:check: - task: coding-standards:composer:apply - - task: compose + - task: ddev vars: - TASK_ARGS: run --rm phpfpm composer normalize --dry-run - - task: compose + TASK_ARGS: composer normalize --dry-run + - task: ddev vars: - TASK_ARGS: run --rm phpfpm composer validate + TASK_ARGS: composer validate coding-standards:markdown:apply: desc: "Apply coding standards for Markdown" @@ -81,16 +58,18 @@ tasks: desc: "Apply coding standards for PHP" cmds: # Cf. .github/workflows/php.yaml - - docker compose run --rm phpfpm vendor/bin/phpcbf - silent: true + - task: ddev + vars: + TASK_ARGS: phpcbf 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 + - task: ddev + vars: + TASK_ARGS: phpcs coding-standards:yaml:apply: desc: "Apply coding standards for YAML" @@ -105,14 +84,46 @@ tasks: # Cf. .github/workflows/yaml.yaml - docker compose run --rm prettier '**/*.{yml,yaml}' --check - test: + code-analysis: + desc: "Run static code analysis" cmds: - - docker compose run --env PHP_XDEBUG_MODE --env PHP_XDEBUG_WITH_REQUEST - --env PHP_IDE_CONFIG --rm phpfpm vendor/bin/phpunit {{.CLI_ARGS}} + - task: code-analysis:phpstan - xdebug:test: + code-analysis:phpstan: + desc: "Run PHPStan" 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}} + - task: ddev + vars: + TASK_ARGS: phpstan + + rector: + desc: "Run rector" + cmds: + - task: ddev + vars: + TASK_ARGS: php vendor/bin/rector + + ddev:start: + desc: Start DDEV setup (https://github.com/ddev/ddev-drupal-contrib) + # https://github.com/ddev/ddev-drupal-contrib + deps: + - composer:install:lenient + cmds: + - ddev add-on get ddev/ddev-drupal-contrib + - ddev restart + - ddev poser --no-interaction + - ddev symlink-project + # Detect expected Drupal and PHP versions. + - ddev config --update + + composer:install:lenient: + cmds: + # Create a temporary composer file to install https://github.com/mglaman/composer-drupal-lenient before the real install needs it. + - for: + - init --no-interaction + - config --no-plugins allow-plugins.mglaman/composer-drupal-lenient true + - require mglaman/composer-drupal-lenient:^1.0 + cmd: ddev exec -- COMPOSER=composer.lenient.json composer {{.ITEM}} + # Clean up + - rm -f composer.lenient.* + internal: true diff --git a/compose.yaml b/compose.yaml index c4f59d0..13f1bcb 100644 --- a/compose.yaml +++ b/compose.yaml @@ -1,17 +1,4 @@ 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 diff --git a/composer.json b/composer.json index ff6cf58..96961dd 100644 --- a/composer.json +++ b/composer.json @@ -17,12 +17,18 @@ "os2forms/os2forms": "^5.1" }, "require-dev": { - "drupal/coder": "^8.3 || ^9.0", + "drupal/coder": "^8.3", "ergebnis/composer-normalize": "^2.52", - "mglaman/composer-drupal-lenient": "^1.0" + "mglaman/composer-drupal-lenient": "^1.0", + "mglaman/phpstan-drupal": "^1.1", + "palantirnet/drupal-rector": "0.x-dev", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^1.0", + "phpstan/phpstan-deprecation-rules": "^1.0" }, "repositories": [ { + "name": "drupal", "type": "composer", "url": "https://packages.drupal.org/8" } @@ -30,25 +36,32 @@ "minimum-stability": "dev", "config": { "allow-plugins": { + "composer/installers": true, "cweagans/composer-patches": true, "dealerdirect/phpcodesniffer-composer-installer": true, - "drupal/core-composer-scaffold": false, + "drupal/core-composer-scaffold": true, + "drupal/core-project-message": true, "ergebnis/composer-normalize": true, "mglaman/composer-drupal-lenient": true, - "simplesamlphp/composer-module-installer": true, - "simplesamlphp/composer-xmlprovider-installer": true, - "symfony/runtime": false, - "vaimo/composer-patches": true, + "php-http/discovery": true, + "phpstan/extension-installer": true, + "simplesamlphp/composer-module-installer": false, + "simplesamlphp/composer-xmlprovider-installer": false, + "tbachert/spi": true, "zaporylie/composer-drupal-optimizations": true + }, + "policy": { + "advisories": { + "block": false + } } }, "extra": { "drupal-lenient": { - "allowed-list": [ - - "drupal/coc_forms_auto_export", - "drupal/webform_node_element" - ] + "allowed-list": [ + "drupal/coc_forms_auto_export", + "drupal/webform_node_element" + ] } } } diff --git a/.phpcs.xml.dist b/phpcs.xml.dist similarity index 95% rename from .phpcs.xml.dist rename to phpcs.xml.dist index 083aa41..0cb04bb 100644 --- a/.phpcs.xml.dist +++ b/phpcs.xml.dist @@ -10,6 +10,8 @@ vendor rector.php + + web diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon new file mode 100644 index 0000000..e69de29 diff --git a/scripts/.env b/scripts/.env deleted file mode 100644 index da1e572..0000000 --- a/scripts/.env +++ /dev/null @@ -1,2 +0,0 @@ -COMPOSE_PROJECT_NAME=drupal-module -MODULE_NAME=os2forms_f2 diff --git a/scripts/base b/scripts/base deleted file mode 100644 index 61d15f4..0000000 --- a/scripts/base +++ /dev/null @@ -1,139 +0,0 @@ -#!/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 <client()->matterSearch('ping'); diff --git a/src/Helper/WebformHelperF2.php b/src/Helper/WebformHelperF2.php index ee7767b..036946f 100644 --- a/src/Helper/WebformHelperF2.php +++ b/src/Helper/WebformHelperF2.php @@ -68,7 +68,7 @@ public function __construct( #[Autowire(service: 'logger.channel.os2forms_f2_submission')] private readonly LoggerChannelInterface $submissionLogger, ) { - /** @var WebformSubmissionStorageInterface $storage */ + /** @var \Drupal\webform\WebformSubmissionStorageInterface $storage */ $storage = $entityTypeManager->getStorage('webform_submission'); $this->webformSubmissionStorage = $storage; $this->queueStorage = $entityTypeManager->getStorage('advancedqueue_queue'); @@ -112,6 +112,7 @@ private function loadQueue(): QueueInterface { * * @phpstan-param array $context */ + #[\Override] public function log($level, $message, array $context = []): void { $this->logger->log($level, $message, $context); // @see https://www.drupal.org/node/3020595 @@ -190,7 +191,7 @@ public function processJob(Job $job): JobResult { $target = $handlerSettings->archive?->archiveTarget; return match ($target) { ArchiveTarget::MatterID => $this->archiveOnMatter($submission, $handlerSettings), - default => throw new RuntimeException(sprintf('Invalid archive target: %s', $target->name)), + default => throw new RuntimeException('Invalid archive target'), }; } catch (\Exception $exception) { @@ -214,7 +215,7 @@ private function replaceTokens(HandlerSettings $handlerSettings, WebformSubmissi } /** - * + * Archive on matter. */ private function archiveOnMatter(WebformSubmissionInterface $submission, HandlerSettings $handlerSettings): JobResult { $matterId = $handlerSettings->archive?->archiveTargetMatter?->matterId; @@ -225,7 +226,8 @@ private function archiveOnMatter(WebformSubmissionInterface $submission, Handler $attachment = $this->getAttachment($submission, $handlerSettings); $message = ''; try { - // Apparently the F2 API inspects the filename extension to determine file type, i.e. we must keep the extension in the temporary filename. + // Apparently the F2 API inspects the filename extension to determine file + // type, i.e. we must keep the extension in the temporary filename. $filePath = $this->fileSystem->saveData($attachment->contents, 'temporary://' . uniqid('os2forms_f2') . '-' . $attachment->filename); $document = new Document(); $document->title = $handlerSettings->archive?->documentTitle ?? $attachment->filename; @@ -244,21 +246,22 @@ private function archiveOnMatter(WebformSubmissionInterface $submission, Handler /** * Get main document. * - * @throws InvalidAttachmentElementException - * * @see WebformAttachmentController::download() + * + * @throws \Drupal\os2forms_f2\Exception\InvalidAttachmentElementException + * If no attachment can be found. */ protected function getAttachment(WebformSubmissionInterface $submission, HandlerSettings $handlerSettings): Attachment { // Lifted from Drupal\webform_attachment\Controller\WebformAttachmentController::download. - $element = $handlerSettings->archive->attachmentElement; - if (NULL === $element) { + $elementKey = $handlerSettings->archive->attachmentElement; + if (NULL === $elementKey) { throw new InvalidAttachmentElementException('Cannot get attachment element'); } - $element = $submission->getWebform()->getElement($element) ?: []; + $element = $submission->getWebform()->getElement($elementKey) ?: []; if (!isset($element['#type'])) { - throw new InvalidAttachmentElementException(sprintf('Cannot get attachment element %s', $element)); + throw new InvalidAttachmentElementException(sprintf('Cannot get attachment element %s', $elementKey)); } - [$type] = explode(':', $element['#type']); + [$type] = explode(':', (string) $element['#type']); $instance = $this->elementInfoManager->createInstance($type); if (!$instance instanceof WebformAttachmentBase) { diff --git a/src/Plugin/AdvancedQueue/JobType/F2.php b/src/Plugin/AdvancedQueue/JobType/F2.php index 3501035..1b1b309 100644 --- a/src/Plugin/AdvancedQueue/JobType/F2.php +++ b/src/Plugin/AdvancedQueue/JobType/F2.php @@ -26,6 +26,7 @@ final class F2 extends JobTypeBase implements ContainerFactoryPluginInterface { * * @phpstan-param array $configuration */ + #[\Override] public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { return new static( $configuration, @@ -55,6 +56,7 @@ public function __construct( /** * {@inheritdoc} */ + #[\Override] public function process(Job $job): JobResult { return $this->helper->processJob($job); } diff --git a/src/Plugin/WebformHandler/WebformHandlerAdditionalStatesTrait.php b/src/Plugin/WebformHandler/WebformHandlerAdditionalStatesTrait.php index a67bc0e..6a7afdb 100644 --- a/src/Plugin/WebformHandler/WebformHandlerAdditionalStatesTrait.php +++ b/src/Plugin/WebformHandler/WebformHandlerAdditionalStatesTrait.php @@ -10,7 +10,7 @@ * * Usage: * - * + * @code * final class WebformHandlerExample extends WebformHandlerBase { * … * use WebformHandlerAdditionalStatesTrait; @@ -47,7 +47,7 @@ * … * } * } - * + * @endcode */ trait WebformHandlerAdditionalStatesTrait { private const string ADDITIONAL = 'additional'; @@ -55,7 +55,7 @@ trait WebformHandlerAdditionalStatesTrait { private const string RESULTS_DISABLED = 'results_disabled'; /** - * See usage in file docblock. + * See code example in file DocBlock. */ private function additionalStatesDefaultConfiguration(): array { return [ @@ -66,7 +66,7 @@ private function additionalStatesDefaultConfiguration(): array { } /** - * See usage in file docblock. + * See code example in file DocBlock. */ private function additionalStatesBuildConfigurationForm(array &$form, FormStateInterface $form_state): void { // Additional. @@ -96,7 +96,7 @@ private function additionalStatesBuildConfigurationForm(array &$form, FormStateI } /** - * See usage in file docblock. + * See code example in file DocBlock. */ private function additionalStatesSubmitConfigurationForm(array $form, FormStateInterface $formState): void { $additional = $formState->getValue(self::ADDITIONAL); @@ -106,7 +106,7 @@ private function additionalStatesSubmitConfigurationForm(array $form, FormStateI } /** - * See usage in file docblock. + * See code example in file DocBlock. */ private function additionalStatesRunOnPostSave(WebformSubmissionInterface $submission): bool { $submissionState = $submission->getWebform()->getSetting(self::RESULTS_DISABLED) ? WebformSubmissionInterface::STATE_COMPLETED : $submission->getState(); diff --git a/src/Plugin/WebformHandler/WebformHandlerF2.php b/src/Plugin/WebformHandler/WebformHandlerF2.php index 3e08607..325ab0d 100644 --- a/src/Plugin/WebformHandler/WebformHandlerF2.php +++ b/src/Plugin/WebformHandler/WebformHandlerF2.php @@ -33,7 +33,12 @@ final class WebformHandlerF2 extends WebformHandlerBase { use StringTranslationTrait; use WebformHandlerAdditionalStatesTrait; - // This ID should match the one assigned in the @WebformHandlerBase incantation. + /** + * Webform handler ID. + * + * This ID should match the one assigned in the @WebformHandlerBase + * incantation. + */ public const string ID = 'os2forms_f2_f2'; /** @@ -69,6 +74,7 @@ public static function create(ContainerInterface $container, array $configuratio * * @phpstan-return array */ + #[\Override] public function defaultConfiguration() { return $this->additionalStatesDefaultConfiguration(); } @@ -76,6 +82,7 @@ public function defaultConfiguration() { /** * {@inheritdoc} */ + #[\Override] public function getOffCanvasWidth(): string { return WebformDialogHelper::DIALOG_NONE; } @@ -128,7 +135,11 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta '#states' => [ 'visible' => [ - ':input[name="settings[' . implode('][', [self::ID, ArchiveSettings::NAME, ArchiveSettings::ARCHIVE_TARGET]) . ']"]' => [ + ':input[name="settings[' . implode('][', [ + self::ID, + ArchiveSettings::NAME, + ArchiveSettings::ARCHIVE_TARGET, + ]) . ']"]' => [ 'value' => ArchiveTarget::MatterID->value, ], ], @@ -151,7 +162,7 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta '#markup' => $matter, ]; } - catch (\Throwable $e) { + catch (\Throwable) { // Ignore all errors. } } @@ -165,6 +176,7 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta /** * {@inheritdoc} */ + #[\Override] public function validateConfigurationForm(array &$form, FormStateInterface $form_state) { parent::validateConfigurationForm($form, $form_state); @@ -196,6 +208,7 @@ public function validateConfigurationForm(array &$form, FormStateInterface $form /** * {@inheritdoc} */ + #[\Override] public function submitConfigurationForm(array &$form, FormStateInterface $form_state) { parent::submitConfigurationForm($form, $form_state); foreach ([ @@ -210,6 +223,7 @@ public function submitConfigurationForm(array &$form, FormStateInterface $form_s /** * {@inheritdoc} */ + #[\Override] public function postSave(WebformSubmissionInterface $webform_submission, $update = TRUE) { if (!$this->additionalStatesRunOnPostSave($webform_submission)) { return; diff --git a/src/Settings/AbstractSettings.php b/src/Settings/AbstractSettings.php index 8c7cfcc..085995c 100644 --- a/src/Settings/AbstractSettings.php +++ b/src/Settings/AbstractSettings.php @@ -150,6 +150,7 @@ public function toArray(bool $recursive = TRUE): array { /** * {@inheritdoc} */ + #[\Override] public function jsonSerialize(): array { return $this->toArray(); }