From 4c1944b5eec1fc572b23e6640b832127903f653e Mon Sep 17 00:00:00 2001 From: jekuaitk Date: Wed, 15 Mar 2023 15:59:22 +0100 Subject: [PATCH 1/5] Added module code --- .github/workflows/pr.yaml | 140 ++++++ .gitignore | 2 + CHANGELOG.md | 11 + README.md | 21 +- composer.json | 56 +++ ...dvancedqueue_queue.get_organized_queue.yml | 11 + os2forms_get_organized.info.yml | 7 + os2forms_get_organized.services.yml | 6 + phpcs.xml.dist | 23 + src/Exception/ArchivingMethodException.php | 9 + .../AttachmentElementNotFoundException.php | 9 + src/Exception/CitizenArchivingException.php | 9 + src/Exception/GetOrganizedCaseIdException.php | 9 + src/Exception/GetOrganizedQueueException.php | 9 + src/Helper/ArchiveHelper.php | 443 ++++++++++++++++++ .../AdvancedQueue/JobType/ArchiveDocument.php | 106 +++++ .../GetOrganizedWebformHandler.php | 294 ++++++++++++ 17 files changed, 1164 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/pr.yaml create mode 100644 .gitignore create mode 100644 CHANGELOG.md create mode 100644 composer.json create mode 100644 config/install/advancedqueue.advancedqueue_queue.get_organized_queue.yml create mode 100644 os2forms_get_organized.info.yml create mode 100644 os2forms_get_organized.services.yml create mode 100644 phpcs.xml.dist create mode 100644 src/Exception/ArchivingMethodException.php create mode 100644 src/Exception/AttachmentElementNotFoundException.php create mode 100644 src/Exception/CitizenArchivingException.php create mode 100644 src/Exception/GetOrganizedCaseIdException.php create mode 100644 src/Exception/GetOrganizedQueueException.php create mode 100644 src/Helper/ArchiveHelper.php create mode 100644 src/Plugin/AdvancedQueue/JobType/ArchiveDocument.php create mode 100644 src/Plugin/WebformHandler/GetOrganizedWebformHandler.php diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml new file mode 100644 index 0000000..143f948 --- /dev/null +++ b/.github/workflows/pr.yaml @@ -0,0 +1,140 @@ +on: pull_request +name: PR Review +jobs: + changelog: + runs-on: ubuntu-latest + name: Changelog should be updated + strategy: + fail-fast: false + steps: + - name: Checkout + uses: actions/checkout@v2 + 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 + + test-composer-files: + name: Validate composer + runs-on: ubuntu-latest + strategy: + matrix: + php-versions: [ '7.4', '8.0', '8.1' ] + dependency-version: [ prefer-lowest, prefer-stable ] + steps: + - uses: actions/checkout@master + - name: Setup PHP, with composer and extensions + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-versions }} + extensions: json + coverage: none + tools: composer:v2 + # https://github.com/shivammathur/setup-php#cache-composer-dependencies + - name: Get composer cache directory + id: composer-cache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + - name: Cache dependencies + uses: actions/cache@v2 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-composer- + - name: Validate composer files + run: | + composer validate --strict composer.json + # Check that dependencies resolve. + composer update --${{ matrix.dependency-version }} --prefer-dist --no-interaction + + php-check-coding-standards: + name: PHP - Check Coding Standards + runs-on: ubuntu-latest + strategy: + matrix: + php-versions: [ '7.4', '8.0', '8.1' ] + dependency-version: [ prefer-lowest, prefer-stable ] + steps: + - uses: actions/checkout@master + - name: Setup PHP, with composer and extensions + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-versions }} + extensions: json + coverage: none + tools: composer:v2 + # https://github.com/shivammathur/setup-php#cache-composer-dependencies + - name: Get composer cache directory + id: composer-cache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + - name: Cache dependencies + uses: actions/cache@v2 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-composer- + - name: Install Dependencies + run: | + composer install --no-interaction --no-progress + - name: PHPCS + run: | + composer coding-standards-check/phpcs + + php-code-analysis: + name: PHP - Code analysis + runs-on: ubuntu-latest + strategy: + matrix: + php-versions: [ '7.4', '8.0', '8.1' ] + dependency-version: [ prefer-lowest, prefer-stable ] + steps: + - uses: actions/checkout@master + - name: Setup PHP, with composer and extensions + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-versions }} + extensions: json, gd + coverage: none + tools: composer:v2 + # https://github.com/shivammathur/setup-php#cache-composer-dependencies + - name: Get composer cache directory + id: composer-cache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + - name: Cache dependencies + uses: actions/cache@v2 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-composer- + - name: drupal-check + run: | + # We need a Drupal project to run drupal-check (cf. https://github.com/mglaman/drupal-check#usage) + # Install Drupal + composer --no-interaction create-project drupal/recommended-project:^9 --stability=dev drupal + # Copy our module source code into the Drupal module folder. + mkdir -p drupal/web/modules/contrib/os2forms_get_organized + cp -r os2forms_get_organized.* composer.json src drupal/web/modules/contrib/os2forms_get_organized + # Add our module as a composer repository. + composer --no-interaction --working-dir=drupal config repositories.os2forms/os2forms_get_organized path web/modules/contrib/os2forms_get_organized + # Restore Drupal composer repository. + composer --no-interaction --working-dir=drupal config repositories.drupal composer https://packages.drupal.org/8 + + composer --no-interaction --working-dir=drupal config --no-plugins allow-plugins.cweagans/composer-patches true + # @see https://getcomposer.org/doc/03-cli.md#modifying-extra-values + composer --no-interaction --working-dir=drupal config --no-plugins --json extra.enable-patching true + + # Require our module. + composer --no-interaction --working-dir=drupal require 'os2forms/os2forms_get_organized:*' + + # Check code + composer --no-interaction --working-dir=drupal require --dev drupal/core-dev + cd drupal/web/modules/contrib/os2forms_get_organized + # Remove our non-dev dependencies to prevent duplicated Drupal installation + # PHP Fatal error: Cannot redeclare drupal_get_filename() (previously declared in /home/runner/work/os2forms_get_organized/os2forms_get_organized/drupal/web/modules/contrib/os2forms_get_organized/vendor/drupal/core/includes/bootstrap.inc:190) in /home/runner/work/os2forms_get_organized/os2forms_get_organized/drupal/web/core/includes/bootstrap.inc on line 190 + # Use sed to remove the "require" property in composer.json + sed -i '/^\s*"require":/,/^\s*}/d' composer.json + composer --no-interaction install + composer code-analysis diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3a9875b --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/vendor/ +composer.lock diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..b311a47 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,11 @@ + +# Changelog + +All notable changes to this project will be documented in this file. + +See [keep a changelog](https://keepachangelog.com/en/1.0.0/) for information +about writing changes to this log. + +## [Unreleased] + +[Unreleased]: https://github.com/OS2Forms/os2forms_get_organized/ diff --git a/README.md b/README.md index 2e013f5..c9117e9 100644 --- a/README.md +++ b/README.md @@ -1 +1,20 @@ -# os2forms_get_organized \ No newline at end of file +# os2forms_get_organized + + +Check coding standards: + +```sh +docker run --rm --interactive --tty --volume ${PWD}:/app itkdev/php7.4-fpm:latest composer install +docker run --rm --interactive --tty --volume ${PWD}:/app itkdev/php7.4-fpm:latest composer coding-standards-check + +docker run --rm --interactive --tty --volume ${PWD}:/app node:18 yarn --cwd /app install +docker run --rm --interactive --tty --volume ${PWD}:/app node:18 yarn --cwd /app coding-standards-check +``` + +Apply coding standards: + +```shell +docker run --rm --interactive --tty --volume ${PWD}:/app itkdev/php7.4-fpm:latest composer coding-standards-apply + +docker run --rm --interactive --tty --volume ${PWD}:/app node:18 yarn --cwd /app coding-standards-apply +``` diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..5249833 --- /dev/null +++ b/composer.json @@ -0,0 +1,56 @@ +{ + "name": "os2forms/os2forms_get_organized", + "description": "OS2Forms GetOrganized integration", + "type": "drupal-module", + "license": "MIT", + "authors": [ + { + "name": "Jeppe Kuhlmann Andersen", + "email": "jekua@aarhus.dk" + } + ], + "minimum-stability": "dev", + "prefer-stable": true, + "repositories": [ + { + "type": "composer", + "url": "https://packages.drupal.org/8" + } + ], + "require": { + "itk-dev/getorganized-api-client-php": "^1.2", + "drupal/webform": "^6.1", + "drupal/advancedqueue": "^1.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "drupal/coder": "^8.3", + "mglaman/drupal-check": "^1.4" + }, + "scripts": { + "code-analysis/drupal-check": [ + "# @see https://github.com/mglaman/drupal-check/issues/261#issuecomment-1030141772 for details on exclude-dir value", + "drupal-check --deprecations --analysis --exclude-dir='vendor,*/Client/*' *.* src" + ], + "code-analysis": [ + "@code-analysis/drupal-check" + ], + "coding-standards-check/phpcs": [ + "vendor/bin/phpcs --standard=phpcs.xml.dist" + ], + "coding-standards-check": [ + "@coding-standards-check/phpcs" + ], + "coding-standards-apply/phpcs": [ + "vendor/bin/phpcbf --standard=phpcs.xml.dist" + ], + "coding-standards-apply": [ + "@coding-standards-apply/phpcs" + ] + }, + "config": { + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true + } + } +} diff --git a/config/install/advancedqueue.advancedqueue_queue.get_organized_queue.yml b/config/install/advancedqueue.advancedqueue_queue.get_organized_queue.yml new file mode 100644 index 0000000..1ae32ba --- /dev/null +++ b/config/install/advancedqueue.advancedqueue_queue.get_organized_queue.yml @@ -0,0 +1,11 @@ +langcode: da +status: true +dependencies: { } +id: get_organized_queue +label: 'Get Organized Queue' +backend: database +backend_configuration: + lease_time: 300 +processor: cron +processing_time: 90 +locked: false diff --git a/os2forms_get_organized.info.yml b/os2forms_get_organized.info.yml new file mode 100644 index 0000000..66e7517 --- /dev/null +++ b/os2forms_get_organized.info.yml @@ -0,0 +1,7 @@ +name: 'OS2Forms GetOrganized' +type: module +description: 'GetOrganized integration' +package: OS2Forms +core_version_requirement: ^9 +dependencies: + - drupal:webform diff --git a/os2forms_get_organized.services.yml b/os2forms_get_organized.services.yml new file mode 100644 index 0000000..0cdefd7 --- /dev/null +++ b/os2forms_get_organized.services.yml @@ -0,0 +1,6 @@ +services: + os2forms_get_organized.archive_helper: + class: Drupal\os2forms_get_organized\Helper\ArchiveHelper + arguments: + - '@config.factory' + - '@entity_type.manager' diff --git a/phpcs.xml.dist b/phpcs.xml.dist new file mode 100644 index 0000000..fc0e2b6 --- /dev/null +++ b/phpcs.xml.dist @@ -0,0 +1,23 @@ + + + The coding standard. + + . + + vendor/ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Exception/ArchivingMethodException.php b/src/Exception/ArchivingMethodException.php new file mode 100644 index 0000000..4c47e12 --- /dev/null +++ b/src/Exception/ArchivingMethodException.php @@ -0,0 +1,9 @@ +config = $config; + $this->entityTypeManager = $entityTypeManager; + } + + /** + * Adds document to GetOrganized case. + * + * @phpstan-param array $handlerConfiguration + */ + public function archive(string $submissionId, array $handlerConfiguration): void { + // Setup Client and services. + if (NULL === $this->client) { + $this->setupClient(); + } + + if (NULL === $this->caseService) { + /** @var \ItkDev\GetOrganized\Service\Cases $caseService */ + $caseService = $this->client->api('cases'); + $this->caseService = $caseService; + } + + if (NULL === $this->documentService) { + /** @var \ItkDev\GetOrganized\Service\Documents $docService */ + $docService = $this->client->api('documents'); + $this->documentService = $docService; + } + + // Detect which archiving method is required. + $archivingMethod = $handlerConfiguration['choose_archiving_method']['archiving_method']; + + if ('archive_to_case_id' === $archivingMethod) { + $this->archiveToCaseId($submissionId, $handlerConfiguration); + } + elseif ('archive_to_citizen' === $archivingMethod) { + $this->archiveToCitizen($submissionId, $handlerConfiguration); + } + + } + + /** + * Sets up Client. + */ + private function setupClient(): void { + $config = $this->config->get('os2forms_get_organized'); + $username = $config->get('username'); + $password = $config->get('password'); + $baseUrl = $config->get('base_url'); + + $this->client = new Client($username, $password, $baseUrl); + } + + /** + * Gets WebformSubmission from id. + * + * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException + * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException + */ + private function getSubmission(string $submissionId): EntityInterface { + $storage = $this->entityTypeManager->getStorage('webform_submission'); + return $storage->load($submissionId); + } + + /** + * Archives document to GetOrganized case id. + * + * @phpstan-param array $handlerConfiguration + */ + private function archiveToCaseId(string $submissionId, array $handlerConfiguration): void { + + /** @var \Drupal\webform\Entity\WebformSubmission $submission */ + $submission = $this->getSubmission($submissionId); + + $getOrganizedCaseId = $handlerConfiguration['choose_archiving_method']['case_id']; + $webformAttachmentElementId = $handlerConfiguration['general']['attachment_element']; + $shouldBeFinalized = $handlerConfiguration['general']['should_be_finalized'] ?? FALSE; + $shouldArchiveFiles = $handlerConfiguration['general']['should_archive_files'] ?? FALSE; + + // Ensure case id exists. + $case = $this->caseService->getByCaseId($getOrganizedCaseId); + + if (!$case) { + $message = sprintf('Could not find a case with id %s.', $getOrganizedCaseId); + throw new GetOrganizedCaseIdException($message); + } + + $this->uploadDocumentToCase($getOrganizedCaseId, $webformAttachmentElementId, $submission, $shouldArchiveFiles, $shouldBeFinalized); + } + + /** + * Archives document to GetOrganized citizen subcase. + * + * @phpstan-param array $handlerConfiguration + */ + private function archiveToCitizen(string $submissionId, array $handlerConfiguration): void { + // Step 1: Find/create parent case + // Step 2: Find/create subcase + // Step 3: Upload to subcase. + if (NULL === $this->client) { + $this->setupClient(); + } + + /** @var \Drupal\webform\Entity\WebformSubmission $submission */ + $submission = $this->getSubmission($submissionId); + + $cprValueElementId = $handlerConfiguration['choose_archiving_method']['cpr_value_element']; + $cprElementValue = $submission->getData()[$cprValueElementId]; + + $cprNameElementId = $handlerConfiguration['choose_archiving_method']['cpr_name_element']; + $cprNameElementValue = $submission->getData()[$cprNameElementId]; + + // Step 1: Find/create parent case. + $caseQuery = [ + 'FieldProperties' => [ + [ + 'InternalName' => 'ows_CCMContactData_CPR', + 'Value' => $cprElementValue, + ], + ], + 'CaseTypePrefixes' => [ + self::CITIZEN_CASE_TYPE_PREFIX, + ], + 'LogicalOperator' => 'AND', + 'ExcludeDeletedCases' => TRUE, + 'ReturnCasesNumber' => 25, + ]; + + $caseResult = $this->caseService->FindByCaseProperties( + $caseQuery + ); + + // Subcases may also contain contain the 'ows_CCMContactData_CPR' property, + // i.e. we need to check result cases are not subcases. + // $caseResult will always contain the 'CasesInfo' key, + // and its value will always be an array. + $caseInfo = array_filter($caseResult['CasesInfo'], function ($caseInfo) { + // Parent cases are always on the form AAA-XXXX-XXXXXX, + // Subcases are always on the form AAA-XXXX-XXXXXX-XXX, + // I.e. we can filter out subcases by checking number of dashes in id. + return 2 === substr_count($caseInfo['CaseID'], '-'); + }); + + $parentCaseCount = count($caseInfo); + + if (0 === $parentCaseCount) { + $parentCaseId = $this->createCitizenCase($cprElementValue, $cprNameElementValue); + } + elseif (1 < $parentCaseCount) { + $message = sprintf('Too many (%d) parent cases.', $parentCaseCount); + throw new CitizenArchivingException($message); + } + else { + $parentCaseId = $caseResult['CasesInfo'][0]['CaseID']; + } + + // Step 2: Find/create subcase. + $subcaseName = $handlerConfiguration['choose_archiving_method']['sub_case_title']; + + $subCasesQuery = [ + 'FieldProperties' => [ + [ + 'InternalName' => 'ows_CaseId', + 'Value' => $parentCaseId . '-', + 'ComparisonType' => 'Contains', + ], + [ + 'InternalName' => 'ows_Title', + 'Value' => $subcaseName, + 'ComparisonType' => 'Equal', + ], + ], + 'CaseTypePrefixes' => [ + self::CITIZEN_CASE_TYPE_PREFIX, + ], + 'LogicalOperator' => 'AND', + 'ExcludeDeletedCases' => TRUE, + // Unsure how many subcases may exist, but fetching 25 should be enough. + 'ReturnCasesNumber' => 25, + ]; + + $subCases = $this->caseService->FindByCaseProperties( + $subCasesQuery + ); + + $subCaseCount = count($subCases['CasesInfo']); + + if (0 === $subCaseCount) { + $subCaseId = $this->createSubCase($parentCaseId, $subcaseName); + } + elseif (1 === $subCaseCount) { + $subCaseId = $subCases['CasesInfo'][0]['CaseID']; + } + else { + $message = sprintf('Too many (%d) subcases with the name %s', $subCaseCount, $subcaseName); + throw new CitizenArchivingException($message); + } + + // Step 3: Upload to subcase. + $webformAttachmentElementId = $handlerConfiguration['general']['attachment_element']; + $shouldBeFinalized = $handlerConfiguration['general']['should_be_finalized'] ?? FALSE; + $shouldArchiveFiles = $handlerConfiguration['general']['should_archive_files'] ?? FALSE; + + $this->uploadDocumentToCase($subCaseId, $webformAttachmentElementId, $submission, $shouldArchiveFiles, $shouldBeFinalized); + } + + /** + * Creates citizen parent case in GetOrganized. + */ + private function createCitizenCase(string $cprElementValue, string $cprNameElementValue): string { + + $metadataArray = [ + 'ows_Title' => $cprElementValue . ' - ' . $cprNameElementValue, + // CCMContactData format: 'name;#ID;#CRP;#CVR;#PNumber', + // We don't create GetOrganized parties (parter) so we leave that empty + // We also don't use cvr- or p-number so we leave those empty. + 'ows_CCMContactData' => $cprNameElementValue . ';#;#' . $cprElementValue . ';#;#', + 'ows_CaseStatus' => 'Åben', + ]; + + $response = $this->caseService->createCase(self::CITIZEN_CASE_TYPE_PREFIX, $metadataArray); + + // Example response. + // {"CaseID":"BOR-2022-000046","CaseRelativeUrl":"\/cases\/BOR12\/BOR-2022-000046",...}. + return $response['CaseID']; + } + + /** + * Creates citizen subcase in GetOrganized. + */ + private function createSubCase(string $caseId, string $caseName): string { + $metadataArray = [ + 'ows_Title' => $caseName, + 'ows_CCMParentCase' => $caseId, + // For creating subcases the 'ows_ContentTypeId' must be set explicitly to + // '0x0100512AABDB08FA4fadB4A10948B5A56C7C01'. + 'ows_ContentTypeId' => '0x0100512AABDB08FA4fadB4A10948B5A56C7C01', + 'ows_CaseStatus' => 'Åben', + ]; + + $response = $this->caseService->createCase(self::CITIZEN_CASE_TYPE_PREFIX, $metadataArray); + + // Example response. + // {"CaseID":"BOR-2022-000046-001","CaseRelativeUrl":"\/cases\/BOR12\/BOR-2022-000046",...}. + return $response['CaseID']; + } + + /** + * Uploads attachment document and attached files to GetOrganized case. + */ + private function uploadDocumentToCase(string $caseId, string $webformAttachmentElementId, WebformSubmission $submission, bool $shouldArchiveFiles, bool $shouldBeFinalized): void { + // Handle main document (the attachment). + $element = $submission->getWebform()->getElement($webformAttachmentElementId); + $fileContent = WebformEntityPrintAttachment::getFileContent($element, $submission); + + // Ids that should possibly be finalized (jornaliseret) later. + $documentIdsForFinalizing = []; + + // Create temp file with attachment-element contents. + $webformLabel = $submission->getWebform()->label(); + $getOrganizedFileName = $webformLabel . '-' . $submission->serial() . '.pdf'; + + $parentDocumentId = $this->archiveDocumentToGetOrganizedCase($caseId, $getOrganizedFileName, $fileContent); + + $documentIdsForFinalizing[] = $parentDocumentId; + + // Handle attached files. + if ($shouldArchiveFiles) { + $fileIds = $this->getFileElementKeysFromSubmission($submission); + + $childDocumentIds = []; + + $fileStorage = $this->entityTypeManager->getStorage('file'); + + foreach ($fileIds as $fileId) { + /** @var \Drupal\file\Entity\File $file */ + $file = $fileStorage->load($fileId); + + $fileContent = file_get_contents($file->getFileUri()); + $getOrganizedFileName = $webformLabel . '-' . $submission->serial() . '-' . $file->getFilename(); + + $childDocumentId = $this->archiveDocumentToGetOrganizedCase($caseId, $getOrganizedFileName, $fileContent); + + $childDocumentIds[] = $childDocumentId; + } + + $documentIdsForFinalizing = array_merge($documentIdsForFinalizing, $childDocumentIds); + + $this->documentService->RelateDocuments($parentDocumentId, $childDocumentIds, 1); + } + + if ($shouldBeFinalized) { + $this->documentService->FinalizeMultiple($documentIdsForFinalizing); + } + } + + /** + * Get available elements by type. + * + * @phpstan-param array $elements + * @phpstan-return array + */ + private function getAvailableElementsByType(string $type, array $elements): array { + $attachmentElements = array_filter($elements, function ($element) use ($type) { + return $type === $element['#type']; + }); + + return array_map(function ($element) { + return $element['#title']; + }, $attachmentElements); + } + + /** + * Archives file content to GetOrganized case. + */ + private function archiveDocumentToGetOrganizedCase(string $caseId, string $getOrganizedFileName, string $fileContent): int { + $tempFile = tempnam('/tmp', $caseId . '-' . uniqid()); + + try { + file_put_contents($tempFile, $fileContent); + + $result = $this->documentService->AddToDocumentLibrary($tempFile, $caseId, $getOrganizedFileName); + + if (!isset($result['DocId'])) { + throw new ArchivingMethodException('Could not get document id from response.'); + } + + $documentId = $result['DocId']; + } finally { + // Remove temp file. + unlink($tempFile); + } + + return (int) $documentId; + } + + /** + * Returns array of file elements keys in submission. + * + * @phpstan-return array + */ + private function getFileElementKeysFromSubmission(WebformSubmission $submission): array { + $elements = $submission->getWebform()->getElementsDecodedAndFlattened(); + + $fileElements = []; + + foreach (self::FILE_ELEMENT_TYPES as $fileElementType) { + $fileElements[] = $this->getAvailableElementsByType($fileElementType, $elements); + } + + // https://dev.to/klnjmm/never-use-arraymerge-in-a-for-loop-in-php-5go1 + $fileElements = array_merge(...$fileElements); + + $elementKeys = array_keys($fileElements); + + $fileIds = []; + + foreach ($elementKeys as $elementKey) { + if (empty($submission->getData()[$elementKey])) { + continue; + } + + // Convert occurrences of singular file into array. + $elementFileIds = (array) $submission->getData()[$elementKey]; + + $fileIds[] = $elementFileIds; + } + + return array_merge(...$fileIds); + } + +} diff --git a/src/Plugin/AdvancedQueue/JobType/ArchiveDocument.php b/src/Plugin/AdvancedQueue/JobType/ArchiveDocument.php new file mode 100644 index 0000000..06f534b --- /dev/null +++ b/src/Plugin/AdvancedQueue/JobType/ArchiveDocument.php @@ -0,0 +1,106 @@ + $configuration + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $container->get('os2forms_get_organized.archive_helper'), + $container->get('logger.factory') + ); + } + + /** + * {@inheritdoc} + * + * @phpstan-param array $configuration + */ + public function __construct( + array $configuration, + $plugin_id, + $plugin_definition, + ArchiveHelper $helper, + LoggerChannelFactoryInterface $loggerFactory + ) { + parent::__construct($configuration, $plugin_id, $plugin_definition); + $this->helper = $helper; + $this->submissionLogger = $loggerFactory->get('webform_submission'); + } + + /** + * Processes the ArchiveDocument job. + */ + public function process(Job $job): JobResult { + try { + $payload = $job->getPayload(); + + /** @var \Drupal\webform\WebformSubmissionInterface $webformSubmission */ + $webformSubmission = WebformSubmission::load($payload['submissionId']); + $logger_context = [ + 'handler_id' => 'os2forms_get_organized', + 'channel' => 'webform_submission', + 'webform_submission' => $webformSubmission, + 'operation' => 'response from queue', + ]; + + try { + $this->helper->archive($payload['submissionId'], $payload['handlerConfiguration']); + $this->submissionLogger->notice($this->t('The submission #@serial was successfully delivered', ['@serial' => $webformSubmission->serial()]), $logger_context); + + return JobResult::success(); + } + catch (\Exception $e) { + $this->submissionLogger->error($this->t('The submission #@serial failed (@message)', [ + '@serial' => $webformSubmission->serial(), + '@message' => $e->getMessage(), + ]), $logger_context); + + return JobResult::failure($e->getMessage()); + } + } + catch (\Exception $e) { + return JobResult::failure($e->getMessage()); + } + } + +} diff --git a/src/Plugin/WebformHandler/GetOrganizedWebformHandler.php b/src/Plugin/WebformHandler/GetOrganizedWebformHandler.php new file mode 100644 index 0000000..992a052 --- /dev/null +++ b/src/Plugin/WebformHandler/GetOrganizedWebformHandler.php @@ -0,0 +1,294 @@ + $configuration + */ + public function __construct(array $configuration, $plugin_id, $plugin_definition, LoggerChannelFactoryInterface $loggerFactory, ConfigFactoryInterface $configFactory, RendererInterface $renderer, EntityTypeManagerInterface $entityTypeManager, WebformSubmissionConditionsValidatorInterface $conditionsValidator, WebformTokenManagerInterface $tokenManager) { + parent::__construct($configuration, $plugin_id, $plugin_definition); + $this->setConfiguration($configuration); + $this->loggerFactory = $loggerFactory; + $this->configFactory = $configFactory; + $this->renderer = $renderer; + $this->entityTypeManager = $entityTypeManager; + $this->conditionsValidator = $conditionsValidator; + $this->tokenManager = $tokenManager; + $this->submissionLogger = $loggerFactory->get('webform_submission'); + } + + /** + * {@inheritdoc} + * + * @phpstan-param array $configuration + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $container->get('logger.factory'), + $container->get('config.factory'), + $container->get('renderer'), + $container->get('entity_type.manager'), + $container->get('webform_submission.conditions_validator'), + $container->get('webform.token_manager') + ); + } + + /** + * {@inheritdoc} + * + * @phpstan-param array $form + * @phpstan-return array + */ + public function buildConfigurationForm(array $form, FormStateInterface $form_state) { + + $elements = $this->getWebform()->getElementsDecodedAndFlattened(); + + $form['general'] = [ + '#type' => 'fieldset', + '#title' => $this->t('Choose general settings'), + ]; + + $form['general']['should_archive_files'] = [ + '#title' => $this->t('Should files be archived?'), + '#type' => 'checkbox', + '#default_value' => $this->configuration['general']['should_archive_files'] ?? FALSE, + '#description' => $this->t('If enabled, files will be archived in GetOrganized.'), + '#required' => FALSE, + ]; + + $form['general']['should_be_finalized'] = [ + '#title' => $this->t('Should documents be finalized?'), + '#type' => 'checkbox', + '#default_value' => $this->configuration['general']['should_be_finalized'] ?? FALSE, + '#description' => $this->t('If enabled, documents will be finalized (journaliseret) in GetOrganized.'), + '#required' => FALSE, + ]; + + $form['general']['attachment_element'] = [ + '#type' => 'select', + '#title' => $this->t('Attachment element'), + '#options' => $this->getAvailableElementsByType('webform_entity_print_attachment:pdf', $elements), + '#default_value' => $this->configuration['general']['attachment_element'] ?? '', + '#description' => $this->t('Choose the element responsible for creating response attachments.'), + '#required' => TRUE, + '#size' => 5, + ]; + + $form['choose_archiving_method'] = [ + '#type' => 'fieldset', + '#title' => $this->t('Choose archiving method'), + ]; + + $form['choose_archiving_method']['archiving_method'] = [ + '#type' => 'select', + '#title' => $this->t('Choose method for archiving attachment'), + '#options' => [ + 'archive_to_case_id' => $this->t('GetOrganized case ID'), + 'archive_to_citizen' => $this->t('Citizen CPR number'), + ], + '#default_value' => $this->configuration['choose_archiving_method']['archiving_method'] ?? 'archive_to_case_id', + '#required' => TRUE, + '#size' => 3, + ]; + + $form['choose_archiving_method']['case_id'] = [ + '#type' => 'textfield', + '#title' => $this->t('GetOrganized case ID'), + '#description' => $this->t('The GetOrganized case that responses should be uploaded to.'), + '#default_value' => $this->configuration['choose_archiving_method']['case_id'] ?? '', + '#states' => [ + 'visible' => [ + [':input[name="settings[choose_archiving_method][archiving_method]"]' => ['value' => 'archive_to_case_id']], + ], + // The only effect this has is showing the required asterisk (*). + // Actual validation happens in validateConfigurationForm. + 'required' => [ + [':input[name="settings[choose_archiving_method][archiving_method]"]' => ['value' => 'archive_to_case_id']], + ], + ], + ]; + + $form['choose_archiving_method']['sub_case_title'] = [ + '#type' => 'textfield', + '#title' => $this->t('GetOrganized case title'), + '#description' => $this->t('The GetOrganized case that responses should be uploaded to. If no case with provided title exists one will be created. If multiple exists nothing will be uploaded.'), + '#default_value' => $this->configuration['choose_archiving_method']['sub_case_title'] ?? '', + '#states' => [ + 'visible' => [ + [':input[name="settings[choose_archiving_method][archiving_method]"]' => ['value' => 'archive_to_citizen']], + ], + // The only effect this has is showing the required asterisk (*). + // Actual validation happens in validateConfigurationForm. + 'required' => [ + [':input[name="settings[choose_archiving_method][archiving_method]"]' => ['value' => 'archive_to_citizen']], + ], + ], + ]; + + $form['choose_archiving_method']['cpr_value_element'] = [ + '#type' => 'select', + '#title' => $this->t('CPR element'), + '#options' => $this->getAvailableElementsByType('cpr_value_element', $elements), + '#default_value' => $this->configuration['choose_archiving_method']['cpr_value_element'] ?? '', + '#description' => $this->t('Choose the element containing CPR number'), + '#size' => 5, + '#states' => [ + 'visible' => [ + [':input[name="settings[choose_archiving_method][archiving_method]"]' => ['value' => 'archive_to_citizen']], + ], + // The only effect this has is showing the required asterisk (*). + // Actual validation happens in validateConfigurationForm. + 'required' => [ + [':input[name="settings[choose_archiving_method][archiving_method]"]' => ['value' => 'archive_to_citizen']], + ], + ], + ]; + + $form['choose_archiving_method']['cpr_name_element'] = [ + '#type' => 'select', + '#title' => $this->t('CPR element'), + '#options' => $this->getAvailableElementsByType('cpr_name_element', $elements), + '#default_value' => $this->configuration['choose_archiving_method']['cpr_name_element'] ?? '', + '#description' => $this->t('Choose the element containing CPR name'), + '#size' => 5, + '#states' => [ + 'visible' => [ + [':input[name="settings[choose_archiving_method][archiving_method]"]' => ['value' => 'archive_to_citizen']], + ], + // The only effect this has is showing the required asterisk (*). + // Actual validation happens in validateConfigurationForm. + 'required' => [ + [':input[name="settings[choose_archiving_method][archiving_method]"]' => ['value' => 'archive_to_citizen']], + ], + ], + ]; + + return $this->setSettingsParents($form); + } + + /** + * {@inheritdoc} + * + * @phpstan-param array $form + */ + public function submitConfigurationForm(array &$form, FormStateInterface $form_state): void { + parent::submitConfigurationForm($form, $form_state); + $this->configuration['general']['should_be_finalized'] = $form_state->getValue('general')['should_be_finalized']; + $this->configuration['general']['should_archive_files'] = $form_state->getValue('general')['should_archive_files']; + $this->configuration['general']['attachment_element'] = $form_state->getValue('general')['attachment_element']; + $this->configuration['choose_archiving_method']['archiving_method'] = $form_state->getValue('choose_archiving_method')['archiving_method']; + $this->configuration['choose_archiving_method']['case_id'] = $form_state->getValue('choose_archiving_method')['case_id']; + $this->configuration['choose_archiving_method']['cpr_value_element'] = $form_state->getValue('choose_archiving_method')['cpr_value_element']; + $this->configuration['choose_archiving_method']['cpr_name_element'] = $form_state->getValue('choose_archiving_method')['cpr_name_element']; + $this->configuration['choose_archiving_method']['sub_case_title'] = $form_state->getValue('choose_archiving_method')['sub_case_title']; + } + + /** + * {@inheritdoc} + * + * @phpstan-param array $form + */ + public function validateConfigurationForm(array &$form, FormStateInterface $form_state): void { + parent::validateConfigurationForm($form, $form_state); + + $configuration = $form_state->getValues(); + if ($configuration['choose_archiving_method']['archiving_method'] === 'archive_to_case_id') { + if (empty($configuration['choose_archiving_method']['case_id'])) { + $form_state->setErrorByName('no_case_id_provided', $this->t('No GetOrganized case ID provided.')); + } + } + + if ($configuration['choose_archiving_method']['archiving_method'] === 'archive_to_citizen') { + if (empty($configuration['choose_archiving_method']['cpr_value_element'])) { + $form_state->setErrorByName('no_cpr_value_element_selected', $this->t('No CPR value element selected.')); + } + if (empty($configuration['choose_archiving_method']['cpr_name_element'])) { + $form_state->setErrorByName('no_cpr_name_element_selected', $this->t('No CPR name element selected.')); + } + if (empty($configuration['choose_archiving_method']['sub_case_title'])) { + $form_state->setErrorByName('no_sub_case_title', $this->t('No GetOrganized case title provided.')); + } + } + + } + + /** + * {@inheritdoc} + */ + public function postSave(WebformSubmissionInterface $webform_submission, $update = TRUE): void { + $queueStorage = $this->entityTypeManager->getStorage('advancedqueue_queue'); + /** @var \Drupal\advancedqueue\Entity\Queue $queue */ + $queue = $queueStorage->load('get_organized_queue'); + $job = Job::create(ArchiveDocument::class, [ + 'submissionId' => $webform_submission->id(), + 'handlerConfiguration' => $this->configuration, + ]); + $queue->enqueueJob($job); + + $logger_context = [ + 'handler_id' => 'os2forms_get_organized', + 'channel' => 'webform_submission', + 'webform_submission' => $webform_submission, + 'operation' => 'submission queued', + ]; + + $this->submissionLogger->notice($this->t('Added submission #@serial to queue for processing', ['@serial' => $webform_submission->serial()]), $logger_context); + } + + /** + * Get available elements by type. + * + * @phpstan-param array $elements + * @phpstan-return array + */ + private function getAvailableElementsByType(string $type, array $elements): array { + $attachmentElements = array_filter($elements, function ($element) use ($type) { + return $type === $element['#type']; + }); + + return array_map(function ($element) { + return $element['#title']; + }, $attachmentElements); + } + +} From 88656fd5ce5677129821662d78173ac211fd99ff Mon Sep 17 00:00:00 2001 From: jekuaitk Date: Fri, 17 Mar 2023 08:49:39 +0100 Subject: [PATCH 2/5] Used KeyValueStore for settings --- README.md | 24 ++- composer.json | 3 +- os2forms_get_organized.info.yml | 2 + os2forms_get_organized.links.menu.yml | 5 + os2forms_get_organized.routing.yml | 7 + os2forms_get_organized.services.yml | 9 +- src/Exception/InvalidSettingException.php | 10 ++ src/Form/SettingsForm.php | 155 ++++++++++++++++++ src/Helper/ArchiveHelper.php | 24 ++- src/Helper/Settings.php | 133 +++++++++++++++ .../AdvancedQueue/JobType/ArchiveDocument.php | 2 +- 11 files changed, 351 insertions(+), 23 deletions(-) create mode 100644 os2forms_get_organized.links.menu.yml create mode 100644 os2forms_get_organized.routing.yml create mode 100644 src/Exception/InvalidSettingException.php create mode 100644 src/Form/SettingsForm.php create mode 100644 src/Helper/Settings.php diff --git a/README.md b/README.md index c9117e9..97b2ce3 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,34 @@ -# os2forms_get_organized +# OS2Forms GetOrganized +Adds [GetOrganized](https://www.getorganized.net/) handler for archiving purposes. + +## Installation + +```sh +composer require os2forms/os2forms_get_organized +vendor/bin/drush pm:enable os2forms_get_organized +``` + +## Configuration + +Configure GetOrganized `username`, `password` and `base url` +on `/admin/os2forms_get_organized/settings`. + +You can also test that the provided +configuration works on `/admin/os2forms_get_organized/settings`. + +## Coding standards Check coding standards: ```sh docker run --rm --interactive --tty --volume ${PWD}:/app itkdev/php7.4-fpm:latest composer install docker run --rm --interactive --tty --volume ${PWD}:/app itkdev/php7.4-fpm:latest composer coding-standards-check - -docker run --rm --interactive --tty --volume ${PWD}:/app node:18 yarn --cwd /app install -docker run --rm --interactive --tty --volume ${PWD}:/app node:18 yarn --cwd /app coding-standards-check ``` Apply coding standards: ```shell docker run --rm --interactive --tty --volume ${PWD}:/app itkdev/php7.4-fpm:latest composer coding-standards-apply - docker run --rm --interactive --tty --volume ${PWD}:/app node:18 yarn --cwd /app coding-standards-apply ``` diff --git a/composer.json b/composer.json index 5249833..338b317 100644 --- a/composer.json +++ b/composer.json @@ -20,7 +20,8 @@ "require": { "itk-dev/getorganized-api-client-php": "^1.2", "drupal/webform": "^6.1", - "drupal/advancedqueue": "^1.0" + "drupal/advancedqueue": "^1.0", + "symfony/options-resolver": "^5.4" }, "require-dev": { "dealerdirect/phpcodesniffer-composer-installer": "^1.0", diff --git a/os2forms_get_organized.info.yml b/os2forms_get_organized.info.yml index 66e7517..1f7f1e2 100644 --- a/os2forms_get_organized.info.yml +++ b/os2forms_get_organized.info.yml @@ -5,3 +5,5 @@ package: OS2Forms core_version_requirement: ^9 dependencies: - drupal:webform + - drupal:advancedqueue +configure: os2forms_get_organized.admin.settings diff --git a/os2forms_get_organized.links.menu.yml b/os2forms_get_organized.links.menu.yml new file mode 100644 index 0000000..14f55a9 --- /dev/null +++ b/os2forms_get_organized.links.menu.yml @@ -0,0 +1,5 @@ +os2forms_get_organized.admin.settings: + title: OS2Forms Get Organized + description: Configure the OS2Forms GetOrganized module + parent: system.admin_config_system + route_name: os2forms_get_organized.admin.settings diff --git a/os2forms_get_organized.routing.yml b/os2forms_get_organized.routing.yml new file mode 100644 index 0000000..60bf9cd --- /dev/null +++ b/os2forms_get_organized.routing.yml @@ -0,0 +1,7 @@ +os2forms_get_organized.admin.settings: + path: '/admin/os2forms_get_organized/settings' + defaults: + _form: '\Drupal\os2forms_get_organized\Form\SettingsForm' + _title: 'GetOrganized settings' + requirements: + _permission: 'administer site configuration' diff --git a/os2forms_get_organized.services.yml b/os2forms_get_organized.services.yml index 0cdefd7..c48dc9e 100644 --- a/os2forms_get_organized.services.yml +++ b/os2forms_get_organized.services.yml @@ -1,6 +1,9 @@ services: - os2forms_get_organized.archive_helper: - class: Drupal\os2forms_get_organized\Helper\ArchiveHelper + Drupal\os2forms_get_organized\Helper\Settings: + arguments: + - "@keyvalue" + + Drupal\os2forms_get_organized\Helper\ArchiveHelper: arguments: - - '@config.factory' - '@entity_type.manager' + - '@Drupal\os2forms_get_organized\Helper\Settings' diff --git a/src/Exception/InvalidSettingException.php b/src/Exception/InvalidSettingException.php new file mode 100644 index 0000000..4736624 --- /dev/null +++ b/src/Exception/InvalidSettingException.php @@ -0,0 +1,10 @@ +settings = $settings; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container): SettingsForm { + return new static( + $container->get(Settings::class), + ); + } + + /** + * {@inheritdoc} + */ + public function getFormId() { + return 'os2forms_get_organized_settings'; + } + + /** + * {@inheritdoc} + * + * @phpstan-param array $form + * @phpstan-return array + */ + public function buildForm(array $form, FormStateInterface $form_state): array { + + $form[self::GET_ORGANIZED_USERNAME] = [ + '#type' => 'textfield', + '#title' => $this->t('Username'), + '#required' => TRUE, + '#default_value' => $this->settings->getUsername(), + ]; + + $form[self::GET_ORGANIZED_PASSWORD] = [ + '#type' => 'textfield', + '#title' => $this->t('Password'), + '#required' => TRUE, + '#default_value' => $this->settings->getPassword(), + ]; + + $form[self::GET_ORGANIZED_BASE_URL] = [ + '#type' => 'textfield', + '#title' => $this->t('GetOrganized base url'), + '#required' => TRUE, + '#default_value' => $this->settings->getBaseUrl(), + '#description' => $this->t('GetOrganized base url. Example: "https://ad.go.aarhuskommune.dk/_goapi"'), + ]; + + $form['actions']['#type'] = 'actions'; + + $form['actions']['submit'] = [ + '#type' => 'submit', + '#value' => $this->t('Save settings'), + ]; + + $form['actions']['testSettings'] = [ + '#type' => 'submit', + '#name' => 'testSettings', + '#value' => $this->t('Test provided information'), + ]; + + return $form; + } + + /** + * {@inheritdoc} + * + * @phpstan-param array $form + */ + public function submitForm(array &$form, FormStateInterface $formState): void { + $username = $formState->getValue(self::GET_ORGANIZED_USERNAME); + $password = $formState->getValue(self::GET_ORGANIZED_PASSWORD); + $baseUrl = $formState->getValue(self::GET_ORGANIZED_BASE_URL); + + $triggeringElement = $formState->getTriggeringElement(); + if ('testSettings' === ($triggeringElement['#name'] ?? NULL)) { + $this->testSettings($username, $password, $baseUrl); + return; + } + + try { + $settings[self::GET_ORGANIZED_USERNAME] = $username; + $settings[self::GET_ORGANIZED_PASSWORD] = $password; + $settings[self::GET_ORGANIZED_BASE_URL] = $baseUrl; + + $this->settings->setSettings($settings); + $this->messenger()->addStatus($this->t('Settings saved')); + } + catch (OptionsResolverException $exception) { + $this->messenger()->addError($this->t('Settings not saved (@message)', ['@message' => $exception->getMessage()])); + } + + $this->messenger()->addStatus($this->t('Settings saved')); + } + + /** + * Test settings by making some arbitrary call to the GetOrganized API. + */ + private function testSettings(string $username, string $password, string $baseUrl): void { + try { + $client = new Client($username, $password, $baseUrl); + /** @var \ItkDev\GetOrganized\Service\Tiles $tileService */ + $tileService = $client->api('tiles'); + + $result = $tileService->GetTilesNavigation(); + + if (empty($result)) { + $message = $this->t('Error occurred while testing the GetOrganized API with provided settings.'); + $this->messenger()->addError($message); + } + else { + $this->messenger()->addStatus($this->t('Settings succesfully tested')); + } + } + catch (\Throwable $throwable) { + $message = $this->t('Error testing provided information: %message', ['%message' => $throwable->getMessage()]); + $this->messenger()->addError($message); + } + } + +} diff --git a/src/Helper/ArchiveHelper.php b/src/Helper/ArchiveHelper.php index 75cf904..da7ee76 100644 --- a/src/Helper/ArchiveHelper.php +++ b/src/Helper/ArchiveHelper.php @@ -2,7 +2,6 @@ namespace Drupal\os2forms_get_organized\Helper; -use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\os2forms_get_organized\Exception\ArchivingMethodException; @@ -43,18 +42,18 @@ class ArchiveHelper { private ?Cases $caseService = NULL; /** - * The ConfigFactoryInterface. + * The EntityTypeManagerInterface. * - * @var \Drupal\Core\Config\ConfigFactoryInterface + * @var \Drupal\Core\Entity\EntityTypeManagerInterface */ - private ConfigFactoryInterface $config; + private EntityTypeManagerInterface $entityTypeManager; /** - * The EntityTypeManagerInterface. + * The settings. * - * @var \Drupal\Core\Entity\EntityTypeManagerInterface + * @var \Drupal\os2forms_get_organized\Helper\Settings */ - private EntityTypeManagerInterface $entityTypeManager; + private Settings $settings; /** * File element types. @@ -70,9 +69,9 @@ class ArchiveHelper { /** * Constructs an ArchiveHelper object. */ - public function __construct(ConfigFactoryInterface $config, EntityTypeManagerInterface $entityTypeManager) { - $this->config = $config; + public function __construct(EntityTypeManagerInterface $entityTypeManager, Settings $settings) { $this->entityTypeManager = $entityTypeManager; + $this->settings = $settings; } /** @@ -114,10 +113,9 @@ public function archive(string $submissionId, array $handlerConfiguration): void * Sets up Client. */ private function setupClient(): void { - $config = $this->config->get('os2forms_get_organized'); - $username = $config->get('username'); - $password = $config->get('password'); - $baseUrl = $config->get('base_url'); + $username = $this->settings->getUsername(); + $password = $this->settings->getPassword(); + $baseUrl = $this->settings->getBaseUrl(); $this->client = new Client($username, $password, $baseUrl); } diff --git a/src/Helper/Settings.php b/src/Helper/Settings.php new file mode 100644 index 0000000..47f13ab --- /dev/null +++ b/src/Helper/Settings.php @@ -0,0 +1,133 @@ + $defaultSettings + */ + private array $defaultSettings = [ + SettingsForm::GET_ORGANIZED_USERNAME => '', + SettingsForm::GET_ORGANIZED_PASSWORD => '', + SettingsForm::GET_ORGANIZED_BASE_URL => '', + ]; + + /** + * Constructor. + */ + public function __construct(KeyValueFactoryInterface $keyValueFactory) { + $this->store = $keyValueFactory->get($this->collection); + } + + /** + * Get username. + * + * @return string + * The sources. + */ + public function getUsername(): string { + $value = $this->get(SettingsForm::GET_ORGANIZED_USERNAME); + return is_string($value) ? $value : ''; + } + + /** + * Get password. + * + * @return string + * The sources. + */ + public function getPassword(): string { + $value = $this->get(SettingsForm::GET_ORGANIZED_PASSWORD); + return is_string($value) ? $value : ''; + } + + /** + * Get password. + * + * @return string + * The sources. + */ + public function getBaseUrl(): string { + $value = $this->get(SettingsForm::GET_ORGANIZED_BASE_URL); + return is_string($value) ? $value : ''; + } + + /** + * Get a setting value. + * + * @param string $key + * The key. + * @param mixed|null $default + * The default value. + * + * @return mixed + * The setting value. + */ + private function get(string $key, mixed $default = NULL): mixed { + $resolver = $this->getSettingsResolver(); + if (!$resolver->isDefined($key)) { + throw new InvalidSettingException(sprintf('Setting %s is not defined', $key)); + } + + return $this->store->get($key, $default); + } + + /** + * Set settings. + * + * @throws \Symfony\Component\OptionsResolver\Exception\ExceptionInterface + * + * @phpstan-param array $settings + */ + public function setSettings(array $settings): self { + $settings = $this->getSettingsResolver()->resolve($settings); + foreach ($settings as $key => $value) { + $this->store->set($key, $value); + } + + return $this; + } + + /** + * Get settings resolver. + */ + private function getSettingsResolver(): OptionsResolver { + return (new OptionsResolver()) + ->setDefaults($this->defaultSettings) + ->setAllowedTypes(SettingsForm::GET_ORGANIZED_USERNAME, 'string') + ->setAllowedTypes(SettingsForm::GET_ORGANIZED_PASSWORD, 'string') + ->setAllowedTypes(SettingsForm::GET_ORGANIZED_BASE_URL, 'string') + ->setRequired([ + SettingsForm::GET_ORGANIZED_USERNAME, + SettingsForm::GET_ORGANIZED_PASSWORD, + SettingsForm::GET_ORGANIZED_BASE_URL, + ]); + } + +} diff --git a/src/Plugin/AdvancedQueue/JobType/ArchiveDocument.php b/src/Plugin/AdvancedQueue/JobType/ArchiveDocument.php index 06f534b..4e5b79e 100644 --- a/src/Plugin/AdvancedQueue/JobType/ArchiveDocument.php +++ b/src/Plugin/AdvancedQueue/JobType/ArchiveDocument.php @@ -45,7 +45,7 @@ public static function create(ContainerInterface $container, array $configuratio $configuration, $plugin_id, $plugin_definition, - $container->get('os2forms_get_organized.archive_helper'), + $container->get(ArchiveHelper::class), $container->get('logger.factory') ); } From 2658ddf0344c7efd964cbdca41b10bf0d15cb5b1 Mon Sep 17 00:00:00 2001 From: jekuaitk Date: Fri, 17 Mar 2023 12:17:31 +0100 Subject: [PATCH 3/5] Coding standards --- src/Helper/Settings.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Helper/Settings.php b/src/Helper/Settings.php index 47f13ab..9e05c9f 100644 --- a/src/Helper/Settings.php +++ b/src/Helper/Settings.php @@ -30,9 +30,9 @@ final class Settings { * Default setting values. * * @var array - * @phpstan-var array $defaultSettings + * @phpstan-var array */ - private array $defaultSettings = [ + private $defaultSettings = [ SettingsForm::GET_ORGANIZED_USERNAME => '', SettingsForm::GET_ORGANIZED_PASSWORD => '', SettingsForm::GET_ORGANIZED_BASE_URL => '', From eb82f746b7a5587ff3a6ad31b2d12276ac261ec6 Mon Sep 17 00:00:00 2001 From: jekuaitk Date: Wed, 22 Mar 2023 15:46:58 +0100 Subject: [PATCH 4/5] Code cleanup --- README.md | 6 +++--- os2forms_get_organized.info.yml | 1 + src/Exception/AttachmentElementNotFoundException.php | 9 --------- src/Helper/Settings.php | 2 +- src/Plugin/AdvancedQueue/JobType/ArchiveDocument.php | 2 +- 5 files changed, 6 insertions(+), 14 deletions(-) delete mode 100644 src/Exception/AttachmentElementNotFoundException.php diff --git a/README.md b/README.md index 97b2ce3..98e0fae 100644 --- a/README.md +++ b/README.md @@ -9,13 +9,13 @@ composer require os2forms/os2forms_get_organized vendor/bin/drush pm:enable os2forms_get_organized ``` -## Configuration +## Settings -Configure GetOrganized `username`, `password` and `base url` +Set GetOrganized `username`, `password` and `base url` on `/admin/os2forms_get_organized/settings`. You can also test that the provided -configuration works on `/admin/os2forms_get_organized/settings`. +details work on `/admin/os2forms_get_organized/settings`. ## Coding standards diff --git a/os2forms_get_organized.info.yml b/os2forms_get_organized.info.yml index 1f7f1e2..2e52d5b 100644 --- a/os2forms_get_organized.info.yml +++ b/os2forms_get_organized.info.yml @@ -6,4 +6,5 @@ core_version_requirement: ^9 dependencies: - drupal:webform - drupal:advancedqueue + - drupal:webform_entity_print_attachment configure: os2forms_get_organized.admin.settings diff --git a/src/Exception/AttachmentElementNotFoundException.php b/src/Exception/AttachmentElementNotFoundException.php deleted file mode 100644 index 5ac750f..0000000 --- a/src/Exception/AttachmentElementNotFoundException.php +++ /dev/null @@ -1,9 +0,0 @@ - Date: Mon, 27 Mar 2023 16:39:26 +0200 Subject: [PATCH 5/5] Removed redundant final keywords --- src/Form/SettingsForm.php | 2 +- src/Plugin/WebformHandler/GetOrganizedWebformHandler.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Form/SettingsForm.php b/src/Form/SettingsForm.php index 4f77148..ad6ab88 100644 --- a/src/Form/SettingsForm.php +++ b/src/Form/SettingsForm.php @@ -13,7 +13,7 @@ /** * GetOrganized settings form. */ -final class SettingsForm extends FormBase { +class SettingsForm extends FormBase { use StringTranslationTrait; public const GET_ORGANIZED_USERNAME = 'get_organized_username'; diff --git a/src/Plugin/WebformHandler/GetOrganizedWebformHandler.php b/src/Plugin/WebformHandler/GetOrganizedWebformHandler.php index 992a052..b352894 100644 --- a/src/Plugin/WebformHandler/GetOrganizedWebformHandler.php +++ b/src/Plugin/WebformHandler/GetOrganizedWebformHandler.php @@ -29,7 +29,7 @@ * submission = \Drupal\webform\Plugin\WebformHandlerInterface::SUBMISSION_REQUIRED, * ) */ -final class GetOrganizedWebformHandler extends WebformHandlerBase { +class GetOrganizedWebformHandler extends WebformHandlerBase { /** * The submission logger. *