diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index bc5e177e..c8596fba 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -29,8 +29,8 @@ We're sorry to hear you have a problem. Can you help us solve it by providing th attributes: label: PHP Version description: What version of PHP are you running? Please be as specific as possible - placeholder: "8.4.0" - value: "8.4.0" + placeholder: "8.5.0" + value: "8.5.0" validations: required: true - type: input @@ -38,8 +38,8 @@ We're sorry to hear you have a problem. Can you help us solve it by providing th attributes: label: Laravel Version description: What version of Laravel are you running? Please be as specific as possible - placeholder: "12.0.0" - value: "12.0.0" + placeholder: "13.0.0" + value: "13.0.0" validations: required: true - type: dropdown diff --git a/.github/workflows/composer-audit.yml b/.github/workflows/composer-audit.yml new file mode 100644 index 00000000..72698cfa --- /dev/null +++ b/.github/workflows/composer-audit.yml @@ -0,0 +1,37 @@ +name: Composer audit + +on: + push: + paths: + - 'composer.json' + - 'composer.lock' + pull_request: + paths: + - 'composer.json' + - 'composer.lock' + schedule: + - cron: '0 6 * * 1' + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + composer-audit: + name: composer audit + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@v6.0.2 + + - name: Setup PHP + uses: shivammathur/setup-php@2.37.0 + with: + php-version: '8.5' + coverage: none + + - name: Resolve dependencies and audit + run: | + composer update --no-interaction --no-ansi + composer audit diff --git a/.github/workflows/dependabot-auto-merge.yml b/.github/workflows/dependabot-auto-merge.yml index 5bfa1ad9..b8a14171 100644 --- a/.github/workflows/dependabot-auto-merge.yml +++ b/.github/workflows/dependabot-auto-merge.yml @@ -9,11 +9,13 @@ jobs: dependabot: runs-on: ubuntu-latest if: ${{ github.actor == 'dependabot[bot]' }} + env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true steps: - name: Dependabot metadata id: metadata - uses: dependabot/fetch-metadata@v2.5.0 + uses: dependabot/fetch-metadata@v3.0.0 with: github-token: "${{ secrets.GITHUB_TOKEN }}" diff --git a/.github/workflows/fix-php-code-style-issues.yml b/.github/workflows/fix-php-code-style-issues.yml index 41e61934..14165467 100644 --- a/.github/workflows/fix-php-code-style-issues.yml +++ b/.github/workflows/fix-php-code-style-issues.yml @@ -13,8 +13,17 @@ jobs: - name: Checkout code uses: actions/checkout@v6.0.2 - - name: Fix PHP code style issues - uses: aglipanci/laravel-pint-action@2.6 + - name: Setup PHP + uses: shivammathur/setup-php@2.37.0 + with: + php-version: '8.5' + coverage: none + + - name: Install composer dependencies + uses: ramsey/composer-install@4.0.0 + + - name: Run Laravel Pint + run: vendor/bin/pint - name: Commit changes uses: stefanzweifel/git-auto-commit-action@v7.1.0 diff --git a/.github/workflows/phpstan.yml b/.github/workflows/phpstan.yml index 18023123..5bbb64c8 100644 --- a/.github/workflows/phpstan.yml +++ b/.github/workflows/phpstan.yml @@ -32,7 +32,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@2.37.0 with: - php-version: '8.4' + php-version: '8.5' coverage: none - name: Install composer dependencies diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 69be47a6..0eb9ffb3 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -14,22 +14,22 @@ jobs: max-parallel: 1 matrix: os: [ ubuntu-latest ] - php: [ 8.2, 8.3, 8.4 ] - laravel: [ 12.* ] + php: [ 8.3, 8.4, 8.5 ] + laravel: [ 13.* ] stability: [ prefer-lowest, prefer-stable ] name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.stability }} - ${{ matrix.os }} steps: - name: Checkout code - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Setup PHP uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick, fileinfo - coverage: none + coverage: pcov - name: Setup problem matchers run: | @@ -41,10 +41,10 @@ jobs: composer require "laravel/framework:${{ matrix.laravel }}" --no-interaction --no-update composer update --${{ matrix.stability }} --prefer-dist --no-interaction - - name: Execute tests + - name: Copy PHPUnit configuration run: cp phpunit.xml.dist phpunit.xml - - name: Execute tests + - name: Run Pest tests run: vendor/bin/pest env: DOCUWARE_URL: ${{ secrets.DOCUWARE_URL }} @@ -54,7 +54,7 @@ jobs: - name: Store Log Artifacts if: failure() - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: name: Store report artifacts path: ./vendor/orchestra/testbench-core/laravel/storage/logs diff --git a/composer.json b/composer.json index 409fc869..52b90235 100644 --- a/composer.json +++ b/composer.json @@ -21,24 +21,27 @@ } ], "require": { - "php": "8.2.*|8.3.*|8.4.*", - "guzzlehttp/guzzle": "^7.8", - "illuminate/contracts": "^12.0", - "nesbot/carbon": "^3.8", + "php": "8.3.*|8.4.*|8.5.*", + "guzzlehttp/guzzle": "^7.9.2", + "illuminate/contracts": "^13.0", + "illuminate/support": "^13.0", + "nesbot/carbon": "^3.8.4", "saloonphp/cache-plugin": "^3.1", - "saloonphp/laravel-plugin": "^3.0|^4.0", - "saloonphp/saloon": "^3.0|^4.0", - "spatie/laravel-package-tools": "^1.19" + "saloonphp/laravel-plugin": "^4.0", + "saloonphp/saloon": "^4.0", + "spatie/laravel-package-tools": "^1.19.0" }, "require-dev": { - "laravel/pint": "^1.21", - "larastan/larastan": "^v3.1", - "orchestra/testbench": "^10.0", - "pestphp/pest": "^3.7", - "phpstan/extension-installer": "^1.4", - "phpstan/phpstan-deprecation-rules": "^2.0", - "phpstan/phpstan-phpunit": "^2.0", - "spatie/laravel-ray": "^1.39" + "laravel/pint": "^1.21.1", + "larastan/larastan": "^3.9.3", + "nunomaduro/collision": "^8.9", + "orchestra/testbench": "^11.0", + "pestphp/pest": "^4.4", + "phpdocumentor/reflection-docblock": "^5.6.1", + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan-deprecation-rules": "^2.0.1", + "phpstan/phpstan-phpunit": "^2.0.4", + "spatie/laravel-ray": "^1.40.0" }, "autoload": { "psr-4": { @@ -53,16 +56,16 @@ "scripts": { "post-autoload-dump": "@php ./vendor/bin/testbench package:discover --ansi", "analyse": "vendor/bin/phpstan analyse", - "test": "vendor/bin/pest", + "test": "vendor/bin/pest --no-coverage", "test-coverage": "vendor/bin/pest --coverage", "format": "vendor/bin/pint" }, "config": { "sort-packages": true, "allow-plugins": { - "composer/package-versions-deprecated": true, - "phpstan/extension-installer": true, - "pestphp/pest-plugin": true + "composer/package-versions-deprecated": false, + "pestphp/pest-plugin": true, + "phpstan/extension-installer": true } }, "extra": { diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 0cc2d040..3438d94e 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -2,7 +2,7 @@ includes: - phpstan-baseline.neon parameters: - level: 5 + level: 6 paths: - src - config diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 5d65eeb6..475320c8 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,6 +1,6 @@ diff --git a/src/Connectors/DocuWareConnector.php b/src/Connectors/DocuWareConnector.php index b36fa324..b3a44e0b 100644 --- a/src/Connectors/DocuWareConnector.php +++ b/src/Connectors/DocuWareConnector.php @@ -17,6 +17,7 @@ use Psr\SimpleCache\InvalidArgumentException; use Saloon\Http\Auth\TokenAuthenticator; use Saloon\Http\Connector; +use Saloon\Http\Response; class DocuWareConnector extends Connector { @@ -48,9 +49,13 @@ protected function defaultAuth(): TokenAuthenticator return new TokenAuthenticator($this->getOrCreateNewOAuthToken()); } + protected function oauthTokenCacheTtlSeconds(RequestTokenDto $token): int + { + return max(1, $token->expiresIn - 60); + } + /** * @throws InvalidArgumentException - * @throws \Exception */ protected function getOrCreateNewOAuthToken(): string { @@ -58,7 +63,6 @@ protected function getOrCreateNewOAuthToken(): string $cacheKey = 'docuware.oauth.'.$this->configuration->identifier; - // Check if the token exists in cache and return it if found if ($cache->has($cacheKey)) { $token = Crypt::decrypt($cache->get($cacheKey)); DocuWareOAuthLog::dispatch($this->configuration->url, $this->configuration->username, 'Token retrieved from cache'); @@ -66,24 +70,19 @@ protected function getOrCreateNewOAuthToken(): string return $token->accessToken; } - // Handle token retrieval for ConfigWithCredentials if ($this->configuration instanceof ConfigWithCredentials) { $token = $this->getNewOAuthTokenWithCredentials(); DocuWareOAuthLog::dispatch($this->configuration->url, $this->configuration->username, 'Token retrieved from API'); - $cache->put($cacheKey, Crypt::encrypt($token), $token->expiresIn - 60); + $cache->put($cacheKey, Crypt::encrypt($token), $this->oauthTokenCacheTtlSeconds($token)); return $token->accessToken; } - // Handle token retrieval for ConfigWithCredentialsTrustedUser - // @phpstan-ignore-next-line - if ($this->configuration instanceof ConfigWithCredentialsTrustedUser) { - $token = $this->getNewOAuthTokenWithCredentialsTrustedUser(); - DocuWareOAuthLog::dispatch($this->configuration->url, $this->configuration->username, 'Token retrieved from API'); - $cache->put($cacheKey, Crypt::encrypt($token), $token->expiresIn - 60); + $token = $this->getNewOAuthTokenWithCredentialsTrustedUser(); + DocuWareOAuthLog::dispatch($this->configuration->url, $this->configuration->username, 'Token retrieved from API'); + $cache->put($cacheKey, Crypt::encrypt($token), $this->oauthTokenCacheTtlSeconds($token)); - return $token->accessToken; - } + return $token->accessToken; } protected function getAuthenticationTokenEndpoint(): IdentityServiceConfiguration @@ -111,18 +110,7 @@ protected function getNewOAuthTokenWithCredentials(): RequestTokenDto password: $this->configuration->password, ))->send(); - throw_if( - $requestTokenResponse->failed(), - trim(preg_replace('/\s\s+/', ' ', Arr::get( - array: $requestTokenResponse->json(), - key: 'error_description', - default: $requestTokenResponse->body() - ))) - ); - - throw_if($requestTokenResponse->dto() == null, 'Token response is null'); - - return $requestTokenResponse->dto(); + return $this->ensureRequestTokenSuccess($requestTokenResponse); } /** @@ -140,6 +128,15 @@ protected function getNewOAuthTokenWithCredentialsTrustedUser(): RequestTokenDto impersonateName: $this->configuration->impersonatedUsername, ))->send(); + return $this->ensureRequestTokenSuccess($requestTokenResponse); + } + + /** + * @throws \Throwable + * @throws \JsonException + */ + protected function ensureRequestTokenSuccess(Response $requestTokenResponse): RequestTokenDto + { throw_if( $requestTokenResponse->failed(), trim(preg_replace('/\s\s+/', ' ', Arr::get( diff --git a/src/DTO/Authentication/OAuth/IdentityServiceConfiguration.php b/src/DTO/Authentication/OAuth/IdentityServiceConfiguration.php index 800c0824..8bd3b5de 100644 --- a/src/DTO/Authentication/OAuth/IdentityServiceConfiguration.php +++ b/src/DTO/Authentication/OAuth/IdentityServiceConfiguration.php @@ -6,6 +6,9 @@ final class IdentityServiceConfiguration { + /** + * @param array $data + */ public static function make(array $data): self { return new self( @@ -26,26 +29,41 @@ public static function make(array $data): self frontchannelLogoutSessionSupported: Arr::get($data, 'frontchannel_logout_session_supported'), backchannelLogoutSupported: Arr::get($data, 'backchannel_logout_supported'), backchannelLogoutSessionSupported: Arr::get($data, 'backchannel_logout_session_supported'), - scopesSupported: Arr::get($data, 'scopes_supported', []), - claimsSupported: Arr::get($data, 'claims_supported', []), - grantTypesSupported: Arr::get($data, 'grant_types_supported', []), - responseTypesSupported: Arr::get($data, 'response_types_supported', []), - responseModesSupported: Arr::get($data, 'response_modes_supported', []), - tokenEndpointAuthMethodsSupported: Arr::get($data, 'token_endpoint_auth_methods_supported', []), - idTokenSigningAlgValuesSupported: Arr::get($data, 'id_token_signing_alg_values_supported', []), - subjectTypesSupported: Arr::get($data, 'subject_types_supported', []), - codeChallengeMethodsSupported: Arr::get($data, 'code_challenge_methods_supported', []), + scopesSupported: self::stringList(Arr::get($data, 'scopes_supported', [])), + claimsSupported: self::stringList(Arr::get($data, 'claims_supported', [])), + grantTypesSupported: self::stringList(Arr::get($data, 'grant_types_supported', [])), + responseTypesSupported: self::stringList(Arr::get($data, 'response_types_supported', [])), + responseModesSupported: self::stringList(Arr::get($data, 'response_modes_supported', [])), + tokenEndpointAuthMethodsSupported: self::stringList(Arr::get($data, 'token_endpoint_auth_methods_supported', [])), + idTokenSigningAlgValuesSupported: self::stringList(Arr::get($data, 'id_token_signing_alg_values_supported', [])), + subjectTypesSupported: self::stringList(Arr::get($data, 'subject_types_supported', [])), + codeChallengeMethodsSupported: self::stringList(Arr::get($data, 'code_challenge_methods_supported', [])), requestParameterSupported: Arr::get($data, 'request_parameter_supported'), - requestObjectSigningAlgValuesSupported: Arr::get($data, 'request_object_signing_alg_values_supported', []), - promptValuesSupported: Arr::get($data, 'prompt_values_supported', []), + requestObjectSigningAlgValuesSupported: self::stringList(Arr::get($data, 'request_object_signing_alg_values_supported', [])), + promptValuesSupported: self::stringList(Arr::get($data, 'prompt_values_supported', [])), authorizationResponseIssParameterSupported: Arr::get($data, 'authorization_response_iss_parameter_supported'), - backchannelTokenDeliveryModesSupported: Arr::get($data, 'backchannel_token_delivery_modes_supported', []), + backchannelTokenDeliveryModesSupported: self::stringList(Arr::get($data, 'backchannel_token_delivery_modes_supported', [])), backchannelUserCodeParameterSupported: Arr::get($data, 'backchannel_user_code_parameter_supported'), - dpopSigningAlgValuesSupported: Arr::get($data, 'dpop_signing_alg_values_supported', []), + dpopSigningAlgValuesSupported: self::stringList(Arr::get($data, 'dpop_signing_alg_values_supported', [])), windowsAuthEndpoint: Arr::get($data, 'windows_auth_endpoint'), ); } + /** + * @param list $scopesSupported + * @param list $claimsSupported + * @param list $grantTypesSupported + * @param list $responseTypesSupported + * @param list $responseModesSupported + * @param list $tokenEndpointAuthMethodsSupported + * @param list $idTokenSigningAlgValuesSupported + * @param list $subjectTypesSupported + * @param list $codeChallengeMethodsSupported + * @param list $requestObjectSigningAlgValuesSupported + * @param list $promptValuesSupported + * @param list $backchannelTokenDeliveryModesSupported + * @param list $dpopSigningAlgValuesSupported + */ public function __construct( public ?string $issuer, public ?string $jwksUri, @@ -82,4 +100,25 @@ public function __construct( public array $dpopSigningAlgValuesSupported, public ?string $windowsAuthEndpoint, ) {} + + /** + * @return list + */ + private static function stringList(mixed $value): array + { + if (! is_array($value)) { + return []; + } + + $out = []; + foreach (array_values($value) as $item) { + if (is_string($item)) { + $out[] = $item; + } elseif (is_scalar($item)) { + $out[] = (string) $item; + } + } + + return $out; + } } diff --git a/src/DTO/Documents/Document.php b/src/DTO/Documents/Document.php index adfc596e..bb8a2720 100644 --- a/src/DTO/Documents/Document.php +++ b/src/DTO/Documents/Document.php @@ -12,18 +12,21 @@ final class Document { + /** + * @param array $data + */ public static function fromJson(array $data): self { $fields = Arr::has($data, 'Fields') - ? self::convertFields(collect(Arr::get($data, 'Fields'))) + ? self::convertFields(collect(self::listOfFieldArraysFromData($data, 'Fields'))) : null; $sections = Arr::has($data, 'Sections') - ? self::convertSections(collect(Arr::get($data, 'Sections'))) + ? self::convertSections(collect(self::listOfFieldArraysFromData($data, 'Sections'))) : null; $suggestions = Arr::has($data, 'Suggestions') - ? self::convertSuggestions(collect(Arr::get($data, 'Suggestions'))) + ? self::convertSuggestions(collect(self::listOfFieldArraysFromData($data, 'Suggestions'))) : null; return new self( @@ -31,7 +34,7 @@ public static function fromJson(array $data): self file_size: Arr::get($data, 'FileSize'), total_pages: Arr::get($data, 'TotalPages'), title: Arr::get($data, 'Title'), - extension: (Arr::get($fields, 'DWEXTENSION'))->value ?? null, + extension: self::extensionFromFields($fields), content_type: Arr::get($data, 'ContentType'), file_cabinet_id: Arr::get($data, 'FileCabinetId'), intellixTrust: Arr::get($data, 'IntellixTrust'), @@ -43,6 +46,50 @@ public static function fromJson(array $data): self ); } + /** + * @param array $data + * @return list> + */ + protected static function listOfFieldArraysFromData(array $data, string $key): array + { + $raw = Arr::get($data, $key); + if (! is_array($raw)) { + return []; + } + + $out = []; + foreach (array_values($raw) as $item) { + if (is_array($item)) { + $out[] = $item; + } + } + + return $out; + } + + /** + * @param Collection|null $fields + */ + protected static function extensionFromFields(?Collection $fields): ?string + { + if ($fields === null) { + return null; + } + + $field = $fields->get('DWEXTENSION'); + if (! $field instanceof DocumentField) { + return null; + } + + $value = $field->value; + + return is_string($value) ? $value : null; + } + + /** + * @param Collection> $fields + * @return Collection + */ protected static function convertFields(Collection $fields): Collection { return $fields->mapWithKeys(function (array $field) { @@ -50,6 +97,10 @@ protected static function convertFields(Collection $fields): Collection }); } + /** + * @param Collection> $suggestions + * @return Collection + */ protected static function convertSuggestions(Collection $suggestions): Collection { return $suggestions->mapWithKeys(function (array $suggestion) { @@ -57,6 +108,10 @@ protected static function convertSuggestions(Collection $suggestions): Collectio }); } + /** + * @param Collection> $sections + * @return Collection + */ protected static function convertSections(Collection $sections): Collection { return $sections->mapWithKeys(function (array $section) { @@ -64,6 +119,11 @@ protected static function convertSections(Collection $sections): Collection }); } + /** + * @param Collection|null $fields + * @param Collection|null $sections + * @param Collection|null $suggestions + */ public function __construct( public int $id, public int $file_size, @@ -118,6 +178,11 @@ public function fileName(): string return "{$name}{$this->extension}"; } + /** + * @param Collection|null $fields + * @param Collection|null $sections + * @param Collection|null $suggestions + */ public static function fake( ?int $id = null, ?int $file_size = null, diff --git a/src/DTO/Documents/DocumentField.php b/src/DTO/Documents/DocumentField.php index d7fe0c13..d38c6b60 100644 --- a/src/DTO/Documents/DocumentField.php +++ b/src/DTO/Documents/DocumentField.php @@ -9,6 +9,9 @@ final class DocumentField { + /** + * @param array $data + */ public static function fromJson(array $data): self { return new self( @@ -21,6 +24,9 @@ public static function fromJson(array $data): self ); } + /** + * @param null|int|float|Carbon|string|Collection $value + */ public function __construct( public bool $systemField, public string $name, diff --git a/src/DTO/Documents/DocumentIndex/IndexDateDTO.php b/src/DTO/Documents/DocumentIndex/IndexDateDTO.php index c794c05f..78f68544 100644 --- a/src/DTO/Documents/DocumentIndex/IndexDateDTO.php +++ b/src/DTO/Documents/DocumentIndex/IndexDateDTO.php @@ -16,7 +16,7 @@ public static function make(string $name, null|Carbon|\Carbon\Carbon $value): se return new self($name, $value); } - public static function makeWithFallback($name, object $value): mixed + public static function makeWithFallback(string $name, object $value): ?self { return match (true) { $value instanceof Carbon => self::make($name, $value), @@ -24,6 +24,9 @@ public static function makeWithFallback($name, object $value): mixed }; } + /** + * @return array + */ public function values(): array { return [ diff --git a/src/DTO/Documents/DocumentIndex/IndexDateTimeDTO.php b/src/DTO/Documents/DocumentIndex/IndexDateTimeDTO.php index 3bc6d581..92067153 100644 --- a/src/DTO/Documents/DocumentIndex/IndexDateTimeDTO.php +++ b/src/DTO/Documents/DocumentIndex/IndexDateTimeDTO.php @@ -16,7 +16,7 @@ public static function make(string $name, null|Carbon|\Carbon\Carbon $value): se return new self($name, $value); } - public static function makeWithFallback($name, object $value): mixed + public static function makeWithFallback(string $name, object $value): ?self { return match (true) { $value instanceof Carbon => self::make($name, $value), @@ -24,6 +24,9 @@ public static function makeWithFallback($name, object $value): mixed }; } + /** + * @return array + */ public function values(): array { return [ diff --git a/src/DTO/Documents/DocumentIndex/IndexDecimalDTO.php b/src/DTO/Documents/DocumentIndex/IndexDecimalDTO.php index e1de1531..72876b66 100644 --- a/src/DTO/Documents/DocumentIndex/IndexDecimalDTO.php +++ b/src/DTO/Documents/DocumentIndex/IndexDecimalDTO.php @@ -14,6 +14,9 @@ public static function make(string $name, null|int|float $value): self return new self($name, $value); } + /** + * @return array + */ public function values(): array { return [ diff --git a/src/DTO/Documents/DocumentIndex/IndexKeywordDTO.php b/src/DTO/Documents/DocumentIndex/IndexKeywordDTO.php index 7b4f960e..62fffa4d 100644 --- a/src/DTO/Documents/DocumentIndex/IndexKeywordDTO.php +++ b/src/DTO/Documents/DocumentIndex/IndexKeywordDTO.php @@ -14,6 +14,9 @@ public static function make(string $name, ?string $value): self return new self($name, $value); } + /** + * @return array + */ public function values(): array { return [ diff --git a/src/DTO/Documents/DocumentIndex/IndexMemoDTO.php b/src/DTO/Documents/DocumentIndex/IndexMemoDTO.php index db8765a1..d59085dd 100644 --- a/src/DTO/Documents/DocumentIndex/IndexMemoDTO.php +++ b/src/DTO/Documents/DocumentIndex/IndexMemoDTO.php @@ -14,6 +14,9 @@ public static function make(string $name, ?string $value): self return new self($name, $value); } + /** + * @return array + */ public function values(): array { return [ diff --git a/src/DTO/Documents/DocumentIndex/IndexNumericDTO.php b/src/DTO/Documents/DocumentIndex/IndexNumericDTO.php index abd2b876..e0365f88 100644 --- a/src/DTO/Documents/DocumentIndex/IndexNumericDTO.php +++ b/src/DTO/Documents/DocumentIndex/IndexNumericDTO.php @@ -16,6 +16,9 @@ public static function make(string $name, ?int $value): self return new self($name, $value); } + /** + * @return array + */ public function values(): array { return [ diff --git a/src/DTO/Documents/DocumentIndex/IndexTableDTO.php b/src/DTO/Documents/DocumentIndex/IndexTableDTO.php index a6b9c321..b37272dd 100644 --- a/src/DTO/Documents/DocumentIndex/IndexTableDTO.php +++ b/src/DTO/Documents/DocumentIndex/IndexTableDTO.php @@ -6,16 +6,25 @@ class IndexTableDTO { + /** + * @param array|Collection|null $rows + */ public function __construct( public string $name, public null|Collection|array $rows, ) {} + /** + * @param array|Collection $rows + */ public static function make(string $name, Collection|array $rows): self { return new self($name, $rows); } + /** + * @return array + */ public function values(): array { return [ @@ -28,16 +37,34 @@ public function values(): array ]; } + /** + * @return list> + */ protected function rowsCollection(): array { - return collect($this->rows ?? [])->map(function ($row) { - return self::makeRowContent(collect($row)); - }) + $rows = $this->rows ?? []; + + $collection = $rows instanceof Collection + ? $rows + : collect($rows); + + return $collection + ->map(function (mixed $row): array { + $rowCollection = $row instanceof Collection + ? $row + : collect(is_array($row) ? $row : []); + + return self::makeRowContent($rowCollection); + }) ->filter() ->values() ->toArray(); } + /** + * @param Collection $indexes + * @return array>> + */ public static function makeRowContent(Collection $indexes): array { return [ diff --git a/src/DTO/Documents/DocumentIndex/IndexTextDTO.php b/src/DTO/Documents/DocumentIndex/IndexTextDTO.php index 8917b1ab..a144fba8 100644 --- a/src/DTO/Documents/DocumentIndex/IndexTextDTO.php +++ b/src/DTO/Documents/DocumentIndex/IndexTextDTO.php @@ -14,6 +14,9 @@ public static function make(string $name, ?string $value): self return new self($name, $value); } + /** + * @return array + */ public function values(): array { return [ diff --git a/src/DTO/Documents/DocumentIndex/PrepareDTO.php b/src/DTO/Documents/DocumentIndex/PrepareDTO.php index 356d4390..cfd245f1 100644 --- a/src/DTO/Documents/DocumentIndex/PrepareDTO.php +++ b/src/DTO/Documents/DocumentIndex/PrepareDTO.php @@ -6,6 +6,10 @@ class PrepareDTO { + /** + * @param Collection $indexes + * @return array + */ public static function makeFields(Collection $indexes): array { return [ @@ -16,6 +20,10 @@ public static function makeFields(Collection $indexes): array ]; } + /** + * @param Collection $indexes + * @return array + */ public static function makeField(Collection $indexes, bool $forceUpdate = false): array { return [ diff --git a/src/DTO/Documents/DocumentPaginator.php b/src/DTO/Documents/DocumentPaginator.php index 422e7862..2319f126 100644 --- a/src/DTO/Documents/DocumentPaginator.php +++ b/src/DTO/Documents/DocumentPaginator.php @@ -8,10 +8,13 @@ use Illuminate\Support\Collection; /** - * @property Collection|Document[] $documents + * @property Collection $documents */ class DocumentPaginator { + /** + * @param Collection $documents + */ public function __construct( public int $total, public int $per_page, @@ -43,6 +46,9 @@ public function failed(): bool return ! $this->successful(); } + /** + * @param array $data + */ public static function fromJson( array $data, int $page, @@ -56,7 +62,17 @@ public static function fromJson( $to = $page === $lastPage ? $total : $page * $perPage; - $documents = collect(Arr::get($data, 'Items'))->map(function (array $document) { + $itemsRaw = Arr::get($data, 'Items', []); + $itemList = []; + if (is_array($itemsRaw)) { + foreach (array_values($itemsRaw) as $item) { + if (is_array($item)) { + $itemList[] = $item; + } + } + } + + $documents = collect($itemList)->map(function (array $document) { return Document::fromJson($document); }); @@ -80,11 +96,14 @@ public static function fromFailed(Exception $e): self last_page: 0, from: 0, to: 0, - documents: collect(), + documents: new Collection, error: ErrorBag::make($e), ); } + /** + * @param Collection|null $documents + */ public static function fake( ?int $total = null, ?int $per_page = null, diff --git a/src/DTO/Documents/TableRow.php b/src/DTO/Documents/TableRow.php index 9612b62b..6b393ca1 100644 --- a/src/DTO/Documents/TableRow.php +++ b/src/DTO/Documents/TableRow.php @@ -6,15 +6,29 @@ class TableRow { + /** + * @param array $data + */ public static function fromJson(array $data): self { - $fields = self::convertFields(collect($data)); + $fieldList = []; + foreach (array_values($data) as $item) { + if (is_array($item)) { + $fieldList[] = $item; + } + } + + $fields = self::convertFields(collect($fieldList)); return new self( fields: $fields, ); } + /** + * @param Collection> $fields + * @return Collection + */ protected static function convertFields(Collection $fields): Collection { return $fields->mapWithKeys(function (array $field) { @@ -22,10 +36,16 @@ protected static function convertFields(Collection $fields): Collection }); } + /** + * @param Collection $fields + */ public function __construct( public Collection $fields, ) {} + /** + * @param Collection|null $fields + */ public static function fake( ?Collection $fields = null, ): self { diff --git a/src/DTO/Documents/TrashDocumentPaginator.php b/src/DTO/Documents/TrashDocumentPaginator.php index c67dcd41..85acebc0 100644 --- a/src/DTO/Documents/TrashDocumentPaginator.php +++ b/src/DTO/Documents/TrashDocumentPaginator.php @@ -8,10 +8,15 @@ use Illuminate\Support\Collection; /** - * @property Collection|Document[] $documents + * @property Collection> $documents */ class TrashDocumentPaginator { + /** + * @param Collection> $headers + * @param Collection> $documents + * @param Collection> $mappedDocuments + */ public function __construct( public int $total, public int $per_page, @@ -45,6 +50,9 @@ public function failed(): bool return ! $this->successful(); } + /** + * @param array $data + */ public static function fromJson( array $data, int $page, @@ -58,14 +66,34 @@ public static function fromJson( $to = $page === $lastPage ? $total : $page * $perPage; - $headers = collect(Arr::get($data, 'Headers')); - $documents = collect(Arr::get($data, 'Rows')); - - $mappedDocuments = $documents->map(function (array $document) use ($headers) { + $headersRaw = Arr::get($data, 'Headers', []); + $headerMap = []; + if (is_array($headersRaw)) { + foreach ($headersRaw as $key => $value) { + if (is_array($value)) { + $headerMap[$key] = $value; + } + } + } + $headers = collect($headerMap); + + $rowsRaw = Arr::get($data, 'Rows', []); + $rowList = []; + if (is_array($rowsRaw)) { + foreach (array_values($rowsRaw) as $row) { + if (is_array($row)) { + $rowList[] = $row; + } + } + } + $documents = collect($rowList); + + $mappedDocuments = $documents->map(function (array $document) use ($headers): Collection { $document = collect($document); - return $document->mapWithKeys(function ($value, $key) use ($headers) { - $header = collect($headers->get($key)); + return $document->mapWithKeys(function (mixed $value, int|string $key) use ($headers): array { + $headerRaw = $headers->get($key); + $header = collect(is_array($headerRaw) ? $headerRaw : []); return $header->has('FieldName') ? [$header->get('FieldName') => $value] : []; })->filter(); @@ -100,6 +128,11 @@ public static function fromFailed(Exception $e): self ); } + /** + * @param Collection>|null $headers + * @param Collection>|null $documents + * @param Collection>|null $mappedDocuments + */ public static function fake( ?int $total = null, ?int $per_page = null, diff --git a/src/DTO/General/Organization/Organization.php b/src/DTO/General/Organization/Organization.php index 26643bc9..13ed220d 100644 --- a/src/DTO/General/Organization/Organization.php +++ b/src/DTO/General/Organization/Organization.php @@ -7,6 +7,9 @@ final class Organization { + /** + * @param array $data + */ public static function fromJson(array $data): self { return new self( @@ -18,6 +21,10 @@ public static function fromJson(array $data): self ); } + /** + * @param array $additionalInfo + * @param array $configurationRights + */ public function __construct( public string $id, public string $name, @@ -26,6 +33,10 @@ public function __construct( public array $configurationRights = [], ) {} + /** + * @param array $additionalInfo + * @param array $configurationRights + */ public static function fake( ?string $id = null, ?string $name = null, diff --git a/src/DTO/SuggestionField.php b/src/DTO/SuggestionField.php index 6e0a11ee..a9510c5c 100644 --- a/src/DTO/SuggestionField.php +++ b/src/DTO/SuggestionField.php @@ -6,6 +6,9 @@ final class SuggestionField { + /** + * @param array $data + */ public static function fromJson(array $data): self { return new self( @@ -16,6 +19,9 @@ public static function fromJson(array $data): self ); } + /** + * @param array $value + */ public function __construct( public array $value, public ?string $name, @@ -23,6 +29,9 @@ public function __construct( public ?string $confidence, ) {} + /** + * @param array $value + */ public static function fake( array $value = [], ?string $name = null, diff --git a/src/DocuWareSearchRequestBuilder.php b/src/DocuWareSearchRequestBuilder.php index 0e70ee76..f3283c67 100644 --- a/src/DocuWareSearchRequestBuilder.php +++ b/src/DocuWareSearchRequestBuilder.php @@ -6,6 +6,7 @@ use CodebarAg\DocuWare\Exceptions\UnableToSearch; use CodebarAg\DocuWare\Requests\Documents\DocumentsTrashBin\GetDocuments; use CodebarAg\DocuWare\Requests\Search\GetSearchRequest; +use Illuminate\Support\Collection; use Illuminate\Support\Str; use Saloon\Exceptions\InvalidResponseClassException; use Saloon\Exceptions\PendingRequestException; @@ -16,6 +17,7 @@ class DocuWareSearchRequestBuilder protected ?string $dialogId = null; + /** @var list */ protected array $additionalFileCabinetIds = []; protected int $page = 1; @@ -28,8 +30,10 @@ class DocuWareSearchRequestBuilder protected string $orderDirection = 'asc'; + /** @var array> */ protected array $filters = []; + /** @var array> */ protected array $usedDateOperators = []; protected bool $trashBin = false; @@ -48,6 +52,9 @@ public function fileCabinet(string $fileCabinetId): self return $this; } + /** + * @param list $fileCabinetIds + */ public function fileCabinets(array $fileCabinetIds): self { $this->fileCabinetId = $fileCabinetIds[0] ?? null; @@ -95,6 +102,10 @@ public function fulltext(?string $searchTerm): self public function filterDate(string $name, string $operator, ?Carbon $date): self { + if ($date === null) { + throw new \InvalidArgumentException('A date is required for date filters.'); + } + $date = $this->exactDateTime($date, $operator); $this->makeSureFilterDateRangeIsCorrect($name, $operator); @@ -140,11 +151,17 @@ public function filterIn(string $name, mixed $values): self return $this->filter($name, $values); } - $values = collect($values)->map(function ($value) { + $list = match (true) { + is_array($values) => array_values($values), + $values instanceof Collection => $values->values()->all(), + default => [], + }; + + $prepared = collect($list)->map(function (mixed $value) { return self::prepareValueForFilter($value); })->toArray(); - $this->filters[$name][] = implode(' OR ', $values); + $this->filters[$name][] = implode(' OR ', $prepared); return $this; } @@ -250,7 +267,7 @@ private function restructureMonoDateFilterRange(): void } } - private function makeSureFilterDateRangeIsCorrect($name, $operator): void + private function makeSureFilterDateRangeIsCorrect(string $name, string $operator): void { if (isset($this->usedDateOperators[$name])) { if ($operatorFilterIndex = array_search($operator, $this->usedDateOperators[$name])) { @@ -270,7 +287,7 @@ private function makeSureFilterDateRangeIsCorrect($name, $operator): void } } - private function exactDateTime($date, $operator): Carbon + private function exactDateTime(Carbon $date, string $operator): Carbon { return match ($operator) { '<', '>=' => $date->startOfDay(), diff --git a/src/Facades/DocuWare.php b/src/Facades/DocuWare.php index cc08cb89..edf46ef1 100644 --- a/src/Facades/DocuWare.php +++ b/src/Facades/DocuWare.php @@ -21,19 +21,19 @@ * @method static string login() * @method static void logout() * @method static Organization getOrganization(string $organizationId) - * @method static Collection|OrganizationIndex[] getOrganizations() - * @method static Collection|FileCabinetInformation[] getFileCabinets() - * @method static Collection|Field[] getFields(string $fileCabinetId) - * @method static Collection|Dialog[] getDialogs(string $fileCabinetId) - * @method static array getSelectList(string $fileCabinetId, string $dialogId, string $fieldName) + * @method static Collection getOrganizations() + * @method static Collection getFileCabinets() + * @method static Collection getFields(string $fileCabinetId) + * @method static Collection getDialogs(string $fileCabinetId) + * @method static array getSelectList(string $fileCabinetId, string $dialogId, string $fieldName) * @method static Document getDocument(string $fileCabinetId, int $documentId) * @method static string getDocumentPreview(string $fileCabinetId, int $documentId) * @method static string downloadDocument(string $fileCabinetId, int $documentId) - * @method static string downloadDocuments(string $fileCabinetId, array $documentIds) - * @method DocumentThumbnail downloadDocumentThumbnail(string $fileCabinetId, int $documentId, int $section, int $page = 0) + * @method static string downloadDocuments(string $fileCabinetId, list $documentIds) + * @method static DocumentThumbnail downloadDocumentThumbnail(string $fileCabinetId, int $documentId, int $section, int $page = 0) * @method static null|int|float|Carbon|string updateDocumentValue(string $fileCabinetId, int $documentId, string $fieldName, string $newValue, bool $forceUpdate = false) - * @method static null|int|float|Carbon|string updateDocumentValues(string $fileCabinetId, int $documentId, array $values, bool $forceUpdate = false) - * @method static Document uploadDocument(string $fileCabinetId, string $fileContent, string $fileName, ?Collection $indexes = null) + * @method static null|int|float|Carbon|string updateDocumentValues(string $fileCabinetId, int $documentId, array $values, bool $forceUpdate = false) + * @method static Document uploadDocument(string $fileCabinetId, string $fileContent, string $fileName, ?Collection $indexes = null) * @method static int documentCount(string $fileCabinetId, string $dialogId) * @method static void deleteDocument(string $fileCabinetId, int $documentId) * @method static DocuWareSearchRequestBuilder search() diff --git a/src/Responses/Documents/UpdateIndexValues/UpdateIndexValuesResponse.php b/src/Responses/Documents/UpdateIndexValues/UpdateIndexValuesResponse.php index 912e998c..2bcce424 100644 --- a/src/Responses/Documents/UpdateIndexValues/UpdateIndexValuesResponse.php +++ b/src/Responses/Documents/UpdateIndexValues/UpdateIndexValuesResponse.php @@ -4,12 +4,16 @@ use CodebarAg\DocuWare\Events\DocuWareResponseLog; use CodebarAg\DocuWare\Support\EnsureValidResponse; +use CodebarAg\DocuWare\Support\JsonArrays; use CodebarAg\DocuWare\Support\ParseValue; use Illuminate\Support\Collection; use Saloon\Http\Response; final class UpdateIndexValuesResponse { + /** + * @return Collection + */ public static function fromResponse(Response $response): Collection { event(new DocuWareResponseLog($response)); @@ -18,7 +22,7 @@ public static function fromResponse(Response $response): Collection $fields = $response->throw()->json('Field'); - return collect($fields)->mapWithKeys(function (array $field) { + return collect(JsonArrays::listOfRecords($fields))->mapWithKeys(function (array $field) { return [ $field['FieldName'] => ParseValue::field($field), ]; diff --git a/src/Responses/Fields/GetFieldsResponse.php b/src/Responses/Fields/GetFieldsResponse.php index bd2b8584..34a8f64a 100644 --- a/src/Responses/Fields/GetFieldsResponse.php +++ b/src/Responses/Fields/GetFieldsResponse.php @@ -5,13 +5,16 @@ use CodebarAg\DocuWare\DTO\Documents\Field; use CodebarAg\DocuWare\Events\DocuWareResponseLog; use CodebarAg\DocuWare\Support\EnsureValidResponse; +use CodebarAg\DocuWare\Support\JsonArrays; use Illuminate\Support\Collection; -use Illuminate\Support\Enumerable; use Saloon\Http\Response; final class GetFieldsResponse { - public static function fromResponse(Response $response): Collection|Enumerable + /** + * @return Collection + */ + public static function fromResponse(Response $response): Collection { event(new DocuWareResponseLog($response)); @@ -19,6 +22,6 @@ public static function fromResponse(Response $response): Collection|Enumerable $fields = $response->throw()->json('Fields'); - return collect($fields)->map(fn (array $field) => Field::fromJson($field)); + return collect(JsonArrays::listOfRecords($fields))->map(fn (array $field) => Field::fromJson($field)); } } diff --git a/src/Responses/FileCabinets/Dialogs/GetAllDialogsResponse.php b/src/Responses/FileCabinets/Dialogs/GetAllDialogsResponse.php index 6e1df2f3..64d0a56c 100644 --- a/src/Responses/FileCabinets/Dialogs/GetAllDialogsResponse.php +++ b/src/Responses/FileCabinets/Dialogs/GetAllDialogsResponse.php @@ -5,13 +5,16 @@ use CodebarAg\DocuWare\DTO\FileCabinets\Dialog; use CodebarAg\DocuWare\Events\DocuWareResponseLog; use CodebarAg\DocuWare\Support\EnsureValidResponse; +use CodebarAg\DocuWare\Support\JsonArrays; use Illuminate\Support\Collection; -use Illuminate\Support\Enumerable; use Saloon\Http\Response; final class GetAllDialogsResponse { - public static function fromResponse(Response $response): Collection|Enumerable + /** + * @return Collection + */ + public static function fromResponse(Response $response): Collection { event(new DocuWareResponseLog($response)); @@ -19,6 +22,6 @@ public static function fromResponse(Response $response): Collection|Enumerable $dialogs = $response->throw()->json('Dialog'); - return collect($dialogs)->map(fn (array $dialog) => Dialog::fromJson($dialog)); + return collect(JsonArrays::listOfRecords($dialogs))->map(fn (array $dialog) => Dialog::fromJson($dialog)); } } diff --git a/src/Responses/General/Organization/GetAllFileCabinetsAndDocumentTraysResponse.php b/src/Responses/General/Organization/GetAllFileCabinetsAndDocumentTraysResponse.php index 52aa8d89..13ff8d75 100644 --- a/src/Responses/General/Organization/GetAllFileCabinetsAndDocumentTraysResponse.php +++ b/src/Responses/General/Organization/GetAllFileCabinetsAndDocumentTraysResponse.php @@ -5,13 +5,16 @@ use CodebarAg\DocuWare\DTO\General\Organization\FileCabinet; use CodebarAg\DocuWare\Events\DocuWareResponseLog; use CodebarAg\DocuWare\Support\EnsureValidResponse; +use CodebarAg\DocuWare\Support\JsonArrays; use Illuminate\Support\Collection; -use Illuminate\Support\Enumerable; use Saloon\Http\Response; final class GetAllFileCabinetsAndDocumentTraysResponse { - public static function fromResponse(Response $response): Collection|Enumerable + /** + * @return Collection + */ + public static function fromResponse(Response $response): Collection { event(new DocuWareResponseLog($response)); @@ -19,6 +22,6 @@ public static function fromResponse(Response $response): Collection|Enumerable $cabinets = $response->throw()->json('FileCabinet'); - return collect($cabinets)->map(fn (array $cabinet) => FileCabinet::fromJson($cabinet)); + return collect(JsonArrays::listOfRecords($cabinets))->map(fn (array $cabinet) => FileCabinet::fromJson($cabinet)); } } diff --git a/src/Responses/General/Organization/GetOrganizationResponse.php b/src/Responses/General/Organization/GetOrganizationResponse.php index a43e41dc..9090b253 100644 --- a/src/Responses/General/Organization/GetOrganizationResponse.php +++ b/src/Responses/General/Organization/GetOrganizationResponse.php @@ -5,13 +5,16 @@ use CodebarAg\DocuWare\DTO\General\Organization\Organization; use CodebarAg\DocuWare\Events\DocuWareResponseLog; use CodebarAg\DocuWare\Support\EnsureValidResponse; +use CodebarAg\DocuWare\Support\JsonArrays; use Illuminate\Support\Collection; -use Illuminate\Support\Enumerable; use Saloon\Http\Response; final class GetOrganizationResponse { - public static function fromResponse(Response $response): Collection|Enumerable + /** + * @return Collection + */ + public static function fromResponse(Response $response): Collection { event(new DocuWareResponseLog($response)); @@ -19,6 +22,6 @@ public static function fromResponse(Response $response): Collection|Enumerable $organizations = $response->throw()->json('Organization'); - return collect($organizations)->map(fn (array $organization) => Organization::fromJson($organization)); + return collect(JsonArrays::listOfRecords($organizations))->map(fn (array $organization) => Organization::fromJson($organization)); } } diff --git a/src/Responses/General/UserManagement/GetModifyGroups/GetGroupsResponse.php b/src/Responses/General/UserManagement/GetModifyGroups/GetGroupsResponse.php index 58228264..41d59ba5 100644 --- a/src/Responses/General/UserManagement/GetModifyGroups/GetGroupsResponse.php +++ b/src/Responses/General/UserManagement/GetModifyGroups/GetGroupsResponse.php @@ -5,13 +5,16 @@ use CodebarAg\DocuWare\DTO\General\UserManagement\GetModifyGroups\Group; use CodebarAg\DocuWare\Events\DocuWareResponseLog; use CodebarAg\DocuWare\Support\EnsureValidResponse; +use CodebarAg\DocuWare\Support\JsonArrays; use Illuminate\Support\Collection; -use Illuminate\Support\Enumerable; use Saloon\Http\Response; final class GetGroupsResponse { - public static function fromResponse(Response $response): Enumerable|Collection + /** + * @return Collection + */ + public static function fromResponse(Response $response): Collection { event(new DocuWareResponseLog($response)); @@ -19,6 +22,6 @@ public static function fromResponse(Response $response): Enumerable|Collection $groups = $response->throw()->json('Item'); - return collect($groups)->map(fn (array $group) => Group::fromJson($group)); + return collect(JsonArrays::listOfRecords($groups))->map(fn (array $group) => Group::fromJson($group)); } } diff --git a/src/Responses/General/UserManagement/GetModifyRoles/GetRolesResponse.php b/src/Responses/General/UserManagement/GetModifyRoles/GetRolesResponse.php index b07ff814..d05c8268 100644 --- a/src/Responses/General/UserManagement/GetModifyRoles/GetRolesResponse.php +++ b/src/Responses/General/UserManagement/GetModifyRoles/GetRolesResponse.php @@ -5,13 +5,16 @@ use CodebarAg\DocuWare\DTO\General\UserManagement\GetModifyRoles\Role; use CodebarAg\DocuWare\Events\DocuWareResponseLog; use CodebarAg\DocuWare\Support\EnsureValidResponse; +use CodebarAg\DocuWare\Support\JsonArrays; use Illuminate\Support\Collection; -use Illuminate\Support\Enumerable; use Saloon\Http\Response; final class GetRolesResponse { - public static function fromResponse(Response $response): Enumerable|Collection + /** + * @return Collection + */ + public static function fromResponse(Response $response): Collection { event(new DocuWareResponseLog($response)); @@ -19,6 +22,6 @@ public static function fromResponse(Response $response): Enumerable|Collection $roles = $response->throw()->json('Item'); - return collect($roles)->map(fn (array $role) => Role::fromJson($role)); + return collect(JsonArrays::listOfRecords($roles))->map(fn (array $role) => Role::fromJson($role)); } } diff --git a/src/Responses/General/UserManagement/GetUsers/GetUsersResponse.php b/src/Responses/General/UserManagement/GetUsers/GetUsersResponse.php index 40104035..4ea47eef 100644 --- a/src/Responses/General/UserManagement/GetUsers/GetUsersResponse.php +++ b/src/Responses/General/UserManagement/GetUsers/GetUsersResponse.php @@ -5,13 +5,16 @@ use CodebarAg\DocuWare\DTO\General\UserManagement\GetUsers\User; use CodebarAg\DocuWare\Events\DocuWareResponseLog; use CodebarAg\DocuWare\Support\EnsureValidResponse; +use CodebarAg\DocuWare\Support\JsonArrays; use Illuminate\Support\Collection; -use Illuminate\Support\Enumerable; use Saloon\Http\Response; final class GetUsersResponse { - public static function fromResponse(Response $response): Enumerable|Collection + /** + * @return Collection + */ + public static function fromResponse(Response $response): Collection { event(new DocuWareResponseLog($response)); @@ -19,6 +22,6 @@ public static function fromResponse(Response $response): Enumerable|Collection $users = $response->throw()->json('User'); - return collect($users)->map(fn (array $user) => User::fromJson($user)); + return collect(JsonArrays::listOfRecords($users))->map(fn (array $user) => User::fromJson($user)); } } diff --git a/src/Responses/Workflow/GetDocumentWorkflowHistoryResponse.php b/src/Responses/Workflow/GetDocumentWorkflowHistoryResponse.php index 0396d9b5..0b8436c6 100644 --- a/src/Responses/Workflow/GetDocumentWorkflowHistoryResponse.php +++ b/src/Responses/Workflow/GetDocumentWorkflowHistoryResponse.php @@ -5,11 +5,15 @@ use CodebarAg\DocuWare\DTO\Workflow\InstanceHistory; use CodebarAg\DocuWare\Events\DocuWareResponseLog; use CodebarAg\DocuWare\Support\EnsureValidResponse; +use CodebarAg\DocuWare\Support\JsonArrays; use Illuminate\Support\Collection; use Saloon\Http\Response; final class GetDocumentWorkflowHistoryResponse { + /** + * @return Collection + */ public static function fromResponse(Response $response): Collection { event(new DocuWareResponseLog($response)); @@ -18,6 +22,6 @@ public static function fromResponse(Response $response): Collection $instanceHistories = $response->throw()->json('InstanceHistory'); - return collect($instanceHistories)->map(fn (array $instanceHistory) => InstanceHistory::fromJson($instanceHistory)); + return collect(JsonArrays::listOfRecords($instanceHistories))->map(fn (array $instanceHistory) => InstanceHistory::fromJson($instanceHistory)); } } diff --git a/src/Support/Auth.php b/src/Support/Auth.php index 2b7887a6..cbf8ad87 100644 --- a/src/Support/Auth.php +++ b/src/Support/Auth.php @@ -18,7 +18,10 @@ class Auth public static function store(CookieJar $cookies): void { - $cookie = collect($cookies->toArray()) + /** @var list> $cookieList */ + $cookieList = $cookies->toArray(); + + $cookie = collect($cookieList) ->reject(fn (array $cookie) => Arr::get($cookie, 'Value') === '') ->firstWhere('Name', self::COOKIE_NAME); @@ -33,9 +36,14 @@ public static function store(CookieJar $cookies): void ); } + /** + * @return array|null + */ public static function cookies(): ?array { - return Cache::driver(self::cacheDriver())->get(self::CACHE_KEY); + $cached = Cache::driver(self::cacheDriver())->get(self::CACHE_KEY); + + return is_array($cached) ? $cached : null; } public static function cookieJar(): ?CookieJar @@ -49,7 +57,9 @@ public static function cookieJar(): ?CookieJar public static function cookieDate(): string { - return Arr::get(Cache::driver(self::cacheDriver())->get(self::CACHE_KEY), 'CreatedAt'); + $cached = Cache::driver(self::cacheDriver())->get(self::CACHE_KEY); + + return is_array($cached) ? (string) Arr::get($cached, 'CreatedAt') : ''; } public static function forget(): void diff --git a/src/Support/JsonArrays.php b/src/Support/JsonArrays.php new file mode 100644 index 00000000..b30405ef --- /dev/null +++ b/src/Support/JsonArrays.php @@ -0,0 +1,25 @@ +> + */ + public static function listOfRecords(mixed $raw): array + { + if (! is_array($raw)) { + return []; + } + + $out = []; + foreach (array_values($raw) as $item) { + if (is_array($item)) { + $out[] = $item; + } + } + + return $out; + } +} diff --git a/src/Support/ParseValue.php b/src/Support/ParseValue.php index 46db420c..16463f05 100644 --- a/src/Support/ParseValue.php +++ b/src/Support/ParseValue.php @@ -10,6 +10,11 @@ class ParseValue { + /** + * @param array|null $field + * @param int|float|Carbon|string|Collection|null $default + * @return int|float|Carbon|string|Collection|null + */ public static function field( ?array $field, int|float|Carbon|string|Collection|null $default = null, @@ -25,9 +30,12 @@ public static function field( 'Int' => (int) $item, 'String' => (string) $item, 'Decimal' => (float) $item, - 'Date', 'DateTime' => self::date($item), - 'Keywords' => Arr::join($item['Keyword'], ', '), - 'Table' => self::table($item), + 'Date', 'DateTime' => self::date(is_string($item) ? $item : ''), + 'Keywords' => Arr::join( + is_array($item) && isset($item['Keyword']) && is_array($item['Keyword']) ? $item['Keyword'] : [], + ', ' + ), + 'Table' => is_array($item) ? self::table($item) : $default, default => $default, }; } @@ -42,20 +50,42 @@ public static function date(string $date): Carbon return Carbon::createFromTimestampMs($timestamp); } + /** + * @param array $Item + * @return Collection|null + */ public static function table(array $Item): ?Collection { - return match ($Item['$type']) { - 'DocumentIndexFieldTable' => self::documentIndexFieldTable($Item['Row']), + $type = $Item['$type'] ?? null; + + return match ($type) { + 'DocumentIndexFieldTable' => isset($Item['Row']) && is_array($Item['Row']) + ? self::documentIndexFieldTable($Item['Row']) + : null, default => null, }; } + /** + * @param array $Row + * @return Collection|null + */ public static function documentIndexFieldTable(array $Row): ?Collection { - $rows = collect($Row); + /** @var list> $list */ + $list = []; + foreach (array_values($Row) as $row) { + if (is_array($row)) { + $list[] = $row; + } + } + + $rows = collect($list); return $rows->map(function (array $row) { - return TableRow::fromJson($row['ColumnValue']); + $columnValue = $row['ColumnValue'] ?? []; + + return TableRow::fromJson(is_array($columnValue) ? $columnValue : []); }); } } diff --git a/tests/Pest.php b/tests/Pest.php index fc959f42..2629eb72 100644 --- a/tests/Pest.php +++ b/tests/Pest.php @@ -3,6 +3,7 @@ use CodebarAg\DocuWare\Connectors\DocuWareConnector; use CodebarAg\DocuWare\DocuWare; use CodebarAg\DocuWare\DTO\Config\ConfigWithCredentials; +use CodebarAg\DocuWare\DTO\Documents\Document; use CodebarAg\DocuWare\Requests\Documents\DocumentsTrashBin\DeleteDocuments; use CodebarAg\DocuWare\Requests\Documents\ModifyDocuments\DeleteDocument; use CodebarAg\DocuWare\Requests\FileCabinets\Search\GetASpecificDocumentFromAFileCabinet; @@ -21,28 +22,37 @@ ->beforeEach(function () { $this->connector = getConnector(); - clearFiles(); + clearFiles($this->connector); }) ->afterEach(function () { - setUsersInactive(); + setUsersInactive($this->connector); }) ->in('Feature'); -function clearFiles(): void +function clearFiles(DocuWareConnector $connector): void { - $connector = getConnector(); + $fileCabinetId = config('laravel-docuware.tests.file_cabinet_id'); - $paginator = $connector->send(new GetDocumentsFromAFileCabinet( - config('laravel-docuware.tests.file_cabinet_id') - ))->dto(); + $paginator = $connector->send(new GetDocumentsFromAFileCabinet($fileCabinetId))->dto(); + + if ($paginator->documents->isEmpty()) { + emptyTrashForConnector($connector); + + return; + } foreach ($paginator->documents as $document) { $connector->send(new DeleteDocument( - config('laravel-docuware.tests.file_cabinet_id'), + $fileCabinetId, $document->id, ))->dto(); } + emptyTrashForConnector($connector); +} + +function emptyTrashForConnector(DocuWareConnector $connector): void +{ $paginatorRequest = (new DocuWare) ->searchRequestBuilder() ->trashBin() @@ -56,10 +66,8 @@ function clearFiles(): void } } -function setUsersInactive(): void +function setUsersInactive(DocuWareConnector $connector): void { - $connector = getConnector(); - $response = $connector->send(new GetUsers); $users = $response->dto()->filter(function ($user) { @@ -76,7 +84,7 @@ function setUsersInactive(): void /** * @throws Throwable */ -function getConnector(): object +function getConnector(): DocuWareConnector { return new DocuWareConnector(new ConfigWithCredentials( username: config('laravel-docuware.credentials.username'), @@ -98,7 +106,38 @@ function cleanup($connector, $fileCabinetId): void } } -function uploadFiles($connector, $fileCabinetId, $path): array +function documentLooksProcessed(Document $document): bool +{ + return $document->total_pages > 0 + && $document->sections !== null + && $document->sections->isNotEmpty(); +} + +function refreshDocumentAfterProcessing(DocuWareConnector $connector, string $fileCabinetId, int $documentId): Document +{ + $maxAttempts = 60; + $sleepMs = 250; + + for ($attempt = 1; $attempt <= $maxAttempts; $attempt++) { + $document = $connector->send(new GetASpecificDocumentFromAFileCabinet( + $fileCabinetId, + $documentId + ))->dto(); + + if (documentLooksProcessed($document)) { + return $document; + } + + Sleep::for($sleepMs)->milliseconds(); + } + + return $connector->send(new GetASpecificDocumentFromAFileCabinet( + $fileCabinetId, + $documentId + ))->dto(); +} + +function uploadFiles(DocuWareConnector $connector, $fileCabinetId, $path): array { $document = $connector->send(new CreateDataRecord( $fileCabinetId, @@ -112,19 +151,8 @@ function uploadFiles($connector, $fileCabinetId, $path): array 'test-2.pdf', ))->dto(); - Sleep::for(5)->seconds(); // Wait for the files to be uploaded and processed - - // Have to get document again as returned data is incorrect - $document = $connector->send(new GetASpecificDocumentFromAFileCabinet( - $fileCabinetId, - $document->id - ))->dto(); - - // Have to get document2 again as returned data is incorrect - $document2 = $connector->send(new GetASpecificDocumentFromAFileCabinet( - $fileCabinetId, - $document2->id - ))->dto(); + $document = refreshDocumentAfterProcessing($connector, $fileCabinetId, $document->id); + $document2 = refreshDocumentAfterProcessing($connector, $fileCabinetId, $document2->id); return [$document, $document2]; } diff --git a/tests/TestCase.php b/tests/TestCase.php index fd588b78..f2b582fc 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -3,37 +3,10 @@ namespace CodebarAg\DocuWare\Tests; use CodebarAg\DocuWare\DocuWareServiceProvider; -use CodebarAg\DocuWare\Events\DocuWareOAuthLog; -use CodebarAg\DocuWare\Events\DocuWareResponseLog; -use Illuminate\Database\Eloquent\Factories\Factory; -use Illuminate\Support\Facades\Event; -use Illuminate\Support\Facades\Log; use Orchestra\Testbench\TestCase as Orchestra; class TestCase extends Orchestra { - protected function setUp(): void - { - parent::setUp(); - - Factory::guessFactoryNamesUsing( - fn (string $modelName) => 'codebar\\DocuWare\\Database\\Factories\\'.class_basename($modelName).'Factory', - ); - - // Event::listen(DocuWareResponseLog::class, function (DocuWareResponseLog $event) { - // Log::info('Docuware response', [ - // $event->response->getPendingRequest()->getUrl(), - // ]); - // }); - // - // Event::listen(DocuWareOAuthLog::class, function (DocuWareOAuthLog $event) { - // Log::info($event->message, [ - // 'url' => $event->url, - // 'username' => $event->username, - // ]); - // }); - } - protected function getPackageProviders($app): array { return [