Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 33 additions & 3 deletions .github/workflows/pr.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:
strategy:
fail-fast: false
matrix:
php: [ '8.1', '8.2', '8.3' ]
php: [ '8.3' ]
name: Validate composer (${{ matrix.php}})
steps:
- name: Checkout
Expand Down Expand Up @@ -55,7 +55,7 @@ jobs:
strategy:
fail-fast: false
matrix:
php: [ '8.1', '8.2', '8.3' ]
php: [ '8.3' ]
name: PHP coding Standards (${{ matrix.php }})
steps:
- name: Checkout
Expand Down Expand Up @@ -85,7 +85,7 @@ jobs:
strategy:
fail-fast: false
matrix:
php: [ '8.1', '8.2', '8.3' ]
php: [ '8.3' ]
name: PHP code analysis (${{ matrix.php }})
steps:
- name: Checkout
Expand Down Expand Up @@ -118,3 +118,33 @@ jobs:
uses: actions/checkout@v4
- name: coding-standards-check
run: docker run --rm --volume ${PWD}:/md peterdavehello/markdownlint markdownlint --ignore vendor --ignore LICENSE.md '**/*.md'

test-unit:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
php: [ '8.3' ]
name: Run unit tests (${{ matrix.php}})
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup PHP, with composer and extensions
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php}}
extensions: apcu, ctype, iconv, imagick, json, redis, soap, xmlreader, zip
coverage: none
- name: Get composer cache directory
id: composer-cache
run: echo "::set-output name=dir::$(composer config cache-files-dir)"
- name: Cache composer dependencies
uses: actions/cache@v4
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ matrix.php }}-composer-${{ hashFiles('**/composer.lock') }}
restore-keys: ${{ matrix.php }}-composer-
- name: Composer install
run: composer install
- name: Run unit tests
run: composer tests/unit
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ generated-classes/tutorial.php
/.php-cs-fixer.cache
/phpcs.xml
###< squizlabs/php_codesniffer ###
*.cache
1 change: 0 additions & 1 deletion .php-version

This file was deleted.

3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

- [PR-41](https://github.com/itk-dev/serviceplatformen/pull/41)
Added support for MeMo 1.2

## [1.6.1] - 2025-03-30

- [PR-39](https://github.com/itk-dev/serviceplatformen/pull/39)
Expand Down
26 changes: 13 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,32 +18,32 @@ generate PHP classes for talking to SOAP services. To update
[resources](./resources) and [generated classes](./generated-classes), run

``` shell
docker run --interactive --tty --rm --volume ${PWD}:/app itkdev/php8.1-fpm:latest composer install
docker run --interactive --tty --rm --volume ${PWD}:/app itkdev/php8.3-fpm:latest composer install
# Update WSDL resources.
docker run --interactive --tty --rm --volume ${PWD}:/app itkdev/php8.1-fpm:latest bin/generate resources
docker run --interactive --tty --rm --volume ${PWD}:/app itkdev/php8.3-fpm:latest bin/generate resources
# Generate PHP classes from WSDL resources.
docker run --interactive --tty --rm --volume ${PWD}:/app itkdev/php8.1-fpm:latest bin/generate classes
docker run --interactive --tty --rm --volume ${PWD}:/app itkdev/php8.3-fpm:latest bin/generate classes
```

## Test commands

``` shell
docker run --rm --volume ${PWD}:/app itkdev/php8.1-fpm:latest vendor/bin/serviceplatformen-sf1601-kombipostafsend --help
docker run --rm --volume ${PWD}:/app itkdev/php8.3-fpm:latest vendor/bin/serviceplatformen-sf1601-kombipostafsend --help
```

Use `bin/serviceplatformen-sf1601-kombipostafsend` (symlinked to
`bin/SF1601/kombipostafsend`) during development of this library. i.e.

``` shell
docker run --interactive --tty --rm --volume ${PWD}:/app itkdev/php8.1-fpm:latest bin/serviceplatformen-sf1601-kombipostafsend
docker run --interactive --tty --rm --volume ${PWD}:/app itkdev/php8.3-fpm:latest bin/serviceplatformen-sf1601-kombipostafsend
```

``` shell
docker run --rm --volume ${PWD}:/app itkdev/php8.1-fpm:latest vendor/bin/serviceplatformen-sf1601-postforespoerg --help
docker run --rm --volume ${PWD}:/app itkdev/php8.3-fpm:latest vendor/bin/serviceplatformen-sf1601-postforespoerg --help
```

``` shell
docker run --interactive --tty --rm --volume ${PWD}:/app itkdev/php8.1-fpm:latest bin/serviceplatformen-sf1601-postforespoerg
docker run --interactive --tty --rm --volume ${PWD}:/app itkdev/php8.3-fpm:latest bin/serviceplatformen-sf1601-postforespoerg
```

## Getting Started
Expand Down Expand Up @@ -79,13 +79,13 @@ composer install
Unit tests:

``` shell
docker run --interactive --tty --rm --volume ${PWD}:/app itkdev/php8.1-fpm:latest composer tests/unit
docker run --interactive --tty --rm --volume ${PWD}:/app itkdev/php8.3-fpm:latest composer tests/unit
```

End to end tests:

``` shell
docker run --interactive --tty --rm --volume ${PWD}:/app itkdev/php8.1-fpm:latest composer tests/end-to-end
docker run --interactive --tty --rm --volume ${PWD}:/app itkdev/php8.3-fpm:latest composer tests/end-to-end
```

### And coding style tests
Expand Down Expand Up @@ -237,9 +237,9 @@ reviewer to merge it for you.
### Coding standards

``` shell
docker run --interactive --tty --rm --volume ${PWD}:/app itkdev/php8.1-fpm:latest composer install
docker run --interactive --tty --rm --volume ${PWD}:/app itkdev/php8.1-fpm:latest composer coding-standards-apply
docker run --interactive --tty --rm --volume ${PWD}:/app itkdev/php8.1-fpm:latest composer coding-standards-check
docker run --interactive --tty --rm --volume ${PWD}:/app itkdev/php8.3-fpm:latest composer install
docker run --interactive --tty --rm --volume ${PWD}:/app itkdev/php8.3-fpm:latest composer coding-standards-apply
docker run --interactive --tty --rm --volume ${PWD}:/app itkdev/php8.3-fpm:latest composer coding-standards-check
```

``` shell
Expand All @@ -250,7 +250,7 @@ docker run --rm --volume ${PWD}:/md peterdavehello/markdownlint markdownlint --i
### Code analysis

``` shell
docker run --interactive --tty --rm --volume ${PWD}:/app --env COMPOSER_MEMORY_LIMIT=-1 itkdev/php8.1-fpm:latest composer code-analysis
docker run --interactive --tty --rm --volume ${PWD}:/app --env COMPOSER_MEMORY_LIMIT=-1 itkdev/php8.3-fpm:latest composer code-analysis
```

## Versioning
Expand Down
26 changes: 23 additions & 3 deletions docs/SF1601.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,20 @@

<https://digitaliseringskataloget.dk/integration/sf1601>

## MeMo version

If the MeMo version is not set explicitly on [a message](lib/DigitalPost/MeMo/Message.php), this library set the version
to [MeMo 1.2](https://digitaliser.dk/digital-post/nyhedsarkiv/2025/mar/lancering-af-memo-version-12).

The default version can be overridden by settings the `SERVICEPLATFORMEN_MEMO_DEFAULT_VERSION` environment variable to
one of the allowed values `1.1` or `1.2`, e.g.

``` env
SERVICEPLATFORMEN_MEMO_DEFAULT_VERSION=1.1
```

------------------------------------------------------------------------------------------------------------------------

Calling this service is extremely simple, but it's extremely hard to navigate the documentation and understand how to
actually authenticate and call the service.

Expand Down Expand Up @@ -182,8 +196,8 @@ classes.
To generate or update the generated PHP classes, run

``` shell
docker run --rm --volume ${PWD}:/app itkdev/php8.1-fpm:latest composer install
docker run --interactive --tty --rm --volume ${PWD}:/app itkdev/php8.1-fpm:latest bin/xsd2php
docker run --rm --volume ${PWD}:/app itkdev/php8.3-fpm:latest composer install
docker run --interactive --tty --rm --volume ${PWD}:/app itkdev/php8.3-fpm:latest bin/xsd2php
```

## Sending test messages
Expand All @@ -192,5 +206,11 @@ A console command, [`bin/SF1601/kombipostafsend`](bin/SF1601/kombipostafsend), c
the “[KombiPostAfsend](https://digitaliseringskataloget.dk/integration/sf1601#table-of-contents-1-2)” service:

``` shell
docker run --interactive --tty --rm --volume ${PWD}:/app itkdev/php8.1-fpm:latest bin/SF1601/kombipostafsend –help
docker run --interactive --tty --rm --volume ${PWD}:/app itkdev/php8.3-fpm:latest bin/SF1601/kombipostafsend –-help
```

To test with another default MeMo version, e.g. `1.1`, run

``` shell
docker run --env SERVICEPLATFORMEN_MEMO_DEFAULT_VERSION=1.1 --interactive --tty --rm --volume ${PWD}:/app itkdev/php8.3-fpm:latest bin/SF1601/kombipostafsend
```
39 changes: 14 additions & 25 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
@@ -1,27 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="./vendor/phpunit/phpunit/phpunit.xsd"
backupGlobals="false"
backupStaticAttributes="false"
bootstrap="vendor/autoload.php"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false">
<testsuites>
<testsuite name="Unit">
<directory suffix="Test.php">./tests/Unit</directory>
</testsuite>

<testsuite name="EndToEnd">
<directory suffix="Test.php">./tests/EndToEnd</directory>
</testsuite>
</testsuites>
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">./src</directory>
</whitelist>
</filter>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd" backupGlobals="false" backupStaticAttributes="false" bootstrap="vendor/autoload.php" colors="true" convertErrorsToExceptions="true" convertNoticesToExceptions="true" convertWarningsToExceptions="true" processIsolation="false" stopOnFailure="false">
<coverage processUncoveredFiles="true">
<include>
<directory suffix=".php">./src</directory>
</include>
</coverage>
<testsuites>
<testsuite name="Unit">
<directory suffix="Test.php">./tests/Unit</directory>
</testsuite>
<testsuite name="EndToEnd">
<directory suffix="Test.php">./tests/EndToEnd</directory>
</testsuite>
</testsuites>
</phpunit>
8 changes: 3 additions & 5 deletions src/Command/SF1601/KombiPostAfsendCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
namespace ItkDev\Serviceplatformen\Command\SF1601;

use DataGovDk\Model\Core\Address;
use DateTime;
use DigitalPost\MeMo\Action;
use DigitalPost\MeMo\AdditionalDocument;
use DigitalPost\MeMo\AttentionData;
Expand All @@ -25,7 +24,6 @@
use DigitalPost\MeMo\Recipient;
use DigitalPost\MeMo\Reservation;
use DigitalPost\MeMo\Sender;
use DOMDocument;
use GuzzleHttp\Client;
use Http\Adapter\Guzzle7\Client as GuzzleAdapter;
use Http\Factory\Guzzle\RequestFactory;
Expand Down Expand Up @@ -206,8 +204,8 @@ private function buildAction(string $spec): Action
->setLabel($options['label']);
if (SF1601::ACTION_AFTALE === $options['action']) {
$reservation = (new Reservation())
->setStartDateTime(new DateTime('+2 days'))
->setEndDateTime(new DateTime('+2 days 1 hour'))
->setStartDateTime(new \DateTime('+2 days'))
->setEndDateTime(new \DateTime('+2 days 1 hour'))
->setLocation('Meeting room 1')
->setAbstract('Abstract')
->setDescription('Description')
Expand Down Expand Up @@ -351,7 +349,7 @@ private function buildMessage(array $options): Message
$message->setMessageHeader($messageHeader);

$body = (new MessageBody())
->setCreatedDateTime(new DateTime());
->setCreatedDateTime(new \DateTime());

if (isset($options['file'])) {
$mimeTypes = new MimeTypes();
Expand Down
2 changes: 1 addition & 1 deletion src/Service/Exception/SoapException.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
*/
class SoapException extends ServiceException
{
public function __construct(readonly private \SoapFault $soapFault, readonly private ?string $request, readonly private ?string $response)
public function __construct(private readonly \SoapFault $soapFault, private readonly ?string $request, private readonly ?string $response)
{
parent::__construct($this->soapFault->getMessage(), $this->soapFault->getCode(), $this->soapFault);
}
Expand Down
66 changes: 64 additions & 2 deletions src/Service/SF1601/SF1601.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ class SF1601 extends AbstractRESTService
self::TYPE_NEM_SMS,
];

public const MEMO_1_1 = 1.1;
public const MEMO_1_2 = 1.2;

public const FORESPOERG_TYPE_DIGITAL_POST = 'digitalpost';
public const FORESPOERG_TYPE_NEM_SMS = 'nemsms';

Expand Down Expand Up @@ -182,9 +185,10 @@ private function buildKombiRequestDocument(string $type, ?Message $message, ?For
if (null !== $message) {
// Set default values on some required attributes.
if (empty($message->getMemoVersion())) {
$message->setMemoVersion(1.1);
$message->setMemoVersion($this->getDefaultMeMoVersion());
}
if (empty($message->getMemoSchVersion())) {
// memoSchVersion is required for MeMo 1.1 only (cf. https://digitaliser.dk/Media/638608781984779669/MeMo%20Versionshistorik%20v1.2.pdf)
if (empty($message->getMemoSchVersion()) && self::MEMO_1_1 === $message->getMemoVersion()) {
$message->setMemoSchVersion('1.1.0');
}

Expand All @@ -203,6 +207,11 @@ private function buildKombiRequestDocument(string $type, ?Message $message, ?For
throw new InvalidMemoException('MeMo message header must have a sender with a label');
}

$this->validateActions($message->getMessageBody()->getMainDocument()->getAction());
foreach ($message->getMessageBody()->getAdditionalDocument() as $document) {
$this->validateActions($document->getAction());
}

// Serialize message and import and append it to kombi_request element.
$messageDocument = Serializer::loadXML((new Serializer())->serialize($message));

Expand All @@ -220,4 +229,57 @@ private function buildKombiRequestDocument(string $type, ?Message $message, ?For

return $document;
}

private function getDefaultMeMoVersion(): float
{
$value = floatval(getenv('SERVICEPLATFORMEN_MEMO_DEFAULT_VERSION'));

return match ($value) {
self::MEMO_1_1 => self::MEMO_1_1,
default => self::MEMO_1_2,
};
}

/**
* Sanitize MeMo filename (cf. https://digitaliser.dk/digital-post/nyhedsarkiv/2024/nov/oeget-validering-i-digital-post)
*
* Non-empty sequences of invalid characters are replaced with a single character.
*/
public static function sanitizeFilename(string $filename, string $replacer = '-'): string
{
// < > : " / \ | ? * CR LF CRLF U+00A0 U+2000 U+2001 U+2002 U+2003 U+2004 U+2005 U+2006 U+2007 U+2008 U+2009 U+200A U+2028 U+205F U+2060 U+3000
$pattern = '@[<>:"/\\\\|?*\x{000D}\x{000A}\x{00A0}\x{2000}\x{2001}\x{2002}\x{2003}\x{2004}\x{2005}\x{2006}\x{2007}\x{2008}\x{2009}\x{200A}\x{2028}\x{205F}\x{2060}\x{3000}]+@u';

if (mb_strlen($replacer) !== 1) {
throw new \InvalidArgumentException(sprintf('Replacer must have length 1 (is %d)', mb_strlen($replacer)));
}

if (preg_match($pattern, $replacer, $matches)) {
throw new \InvalidArgumentException(sprintf('Replacer %s contains invalid character %s', var_export($replacer, true), var_export($matches[0], true)));
}

return preg_replace($pattern, $replacer, $filename);
}

/**
* Validate MeMo actions.
*
* @param \DigitalPost\MeMo\Action[] $actions
*/
private function validateActions(array $actions)
{
foreach ($actions as $action) {
/** @phpstan-ignore nullsafe.neverNull (Action::getEntryPoint can actually return null) */
$url = $action->getEntryPoint()?->getUrl();
// URL must be absolute and use https (cf. https://digitaliser.dk/digital-post/nyhedsarkiv/2024/nov/oeget-validering-i-digital-post)
if ($url && 'https' !== parse_url($url, PHP_URL_SCHEME)) {
throw new \RuntimeException(sprintf(
'URL %s for action "%s" (%s) must be absolute and use the https scheme, i.e. start with "https://".',
$url,
$action->getLabel(),
$action->getActionCode()
));
}
}
}
}
Loading
Loading