From 3d34db3827479bfbd7b8ff25f2190f0d02b65ebe Mon Sep 17 00:00:00 2001 From: Sebastian Fix Date: Fri, 17 Oct 2025 15:57:49 +0200 Subject: [PATCH 01/29] Fixed #197 --- .../General/UserManagement/GetUsers/OutOfOffice.php | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/DTO/General/UserManagement/GetUsers/OutOfOffice.php b/src/DTO/General/UserManagement/GetUsers/OutOfOffice.php index 802e4cf8..2a5a356f 100644 --- a/src/DTO/General/UserManagement/GetUsers/OutOfOffice.php +++ b/src/DTO/General/UserManagement/GetUsers/OutOfOffice.php @@ -12,12 +12,16 @@ public static function fromJson(array $data): self { if ($startDateTime = Arr::get($data, 'StartDateTime')) { $startDateTime = Str::of($startDateTime)->after('(')->before(')'); - $startDateTime = Carbon::createFromTimestamp($startDateTime); + // Extract milliseconds part (ignore optional timezone offset like +0000) + $milliseconds = (int) (string) $startDateTime; + $startDateTime = Carbon::createFromTimestampMs($milliseconds, 'UTC'); } if ($endDateTime = Arr::get($data, 'EndDateTime')) { $endDateTime = Str::of($endDateTime)->after('(')->before(')'); - $endDateTime = Carbon::createFromTimestamp($endDateTime); + // Extract milliseconds part (ignore optional timezone offset like +0000) + $milliseconds = (int) (string) $endDateTime; + $endDateTime = Carbon::createFromTimestampMs($milliseconds, 'UTC'); } return new self( @@ -25,7 +29,7 @@ public static function fromJson(array $data): self startDateTime: $startDateTime, startDateTimeSpecified: Arr::get($data, 'StartDateTimeSpecified'), endDateTime: $endDateTime, - endDateTimeSpecified: Arr::get($data, 'EndDateTimeSpecified'), + endDateTimeSpecified: Arr::get($data, key: 'EndDateTimeSpecified'), ); } From 6087c046c1bf7ea2b798d7ebe97dbe11b8878ffe Mon Sep 17 00:00:00 2001 From: Sebastian Fix Date: Fri, 17 Oct 2025 16:02:52 +0200 Subject: [PATCH 02/29] Resolved #189, updated Docs and Tests for Enums --- .phpunit.cache/test-results | 2 +- README.md | 46 ++++++++++++++ src/Enums/DocuWareFieldTypeEnum.php | 13 ++++ tests/Unit/Enums/ConnectionEnumTest.php | 37 +++++++++++ tests/Unit/Enums/DialogTypeTest.php | 46 ++++++++++++++ .../Unit/Enums/DocuWareFieldTypeEnumTest.php | 61 +++++++++++++++++++ 6 files changed, 204 insertions(+), 1 deletion(-) create mode 100644 src/Enums/DocuWareFieldTypeEnum.php create mode 100644 tests/Unit/Enums/ConnectionEnumTest.php create mode 100644 tests/Unit/Enums/DialogTypeTest.php create mode 100644 tests/Unit/Enums/DocuWareFieldTypeEnumTest.php diff --git a/.phpunit.cache/test-results b/.phpunit.cache/test-results index 865937dc..7c94d9dd 100644 --- a/.phpunit.cache/test-results +++ b/.phpunit.cache/test-results @@ -1 +1 @@ -{"version":"pest_3.8.2","defects":{"P\\Tests\\Feature\\Requests\\Workflow\\GetDocumentWorkflowHistoryTest::__pest_evaluable_it_can_get_document_workflow_history":1,"P\\Tests\\Feature\\SleepTest::__pest_evaluable_sleep":1,"P\\Tests\\Feature\\Laravel12CacheCompatibilityTest::__pest_evaluable_cache_configuration_uses_CACHE__STORE_in_Laravel_12":8,"P\\Tests\\Feature\\Laravel12CacheCompatibilityTest::__pest_evaluable_cache_configuration_uses_DOCUWARE__CACHE__DRIVER_when_explicitly_set":7,"P\\Tests\\Feature\\Requests\\Workflow\\GetDocumentWorkflowHistoryStepsTest::__pest_evaluable_it_can_get_document_workflow_history":1,"P\\Tests\\Feature\\Requests\\FileCabinets\\Upload\\AppendFilesToADataRecordTest::__pest_evaluable_it_can_attach_files_to_a_data_record":1,"Tests\\Unit\\DTO\\TextshotPageTest::test_it_can_create_textshot_page_with_null_language":7,"Tests\\Unit\\DTO\\TextshotPageTest::test_it_can_create_textshot_page_with_string_language":7,"P\\Tests\\Feature\\Laravel12CacheCompatibilityTest::__pest_evaluable_cache_configuration_uses_default_file_driver_when_no_configuration_is_set":8,"P\\Tests\\Feature\\Laravel12CacheCompatibilityTest::__pest_evaluable_cache_configuration_respects_the_new_CACHE__STORE_fallback_chain":8,"P\\Tests\\Feature\\Laravel12CacheCompatibilityTest::__pest_evaluable_environment_variable_fallback_chain_works_correctly":7,"P\\Tests\\Feature\\Laravel12CacheCompatibilityTest::__pest_evaluable_CACHE__STORE_takes_precedence_over_CACHE__DRIVER_when_DOCUWARE__CACHE__DRIVER_is_not_set":7,"P\\Tests\\Feature\\Laravel12CacheCompatibilityTest::__pest_evaluable_cache_configuration_can_be_overridden":8,"P\\Tests\\Feature\\Laravel12CacheCompatibilityTest::__pest_evaluable_cache_configuration_fallback_chain_is_properly_configured":8},"times":{"P\\Tests\\Feature\\Requests\\Documents\\Sections\\GetTextshotTest::__pest_evaluable_it_get_textshot_for_a_specific_section":1.669,"P\\Tests\\Feature\\Requests\\Documents\\DocumentsTrashBin\\DeleteDocumentsFromTrashBinTest::__pest_evaluable_it_can_delete_documents_in_trash":1.262,"P\\Tests\\Feature\\Requests\\Documents\\Sections\\GetAllSectionsFromADocumentTest::__pest_evaluable_it_can_get_all_sections_from_a_document":1.1,"P\\Tests\\Feature\\Requests\\Documents\\ClipUnclipStapleUnstaple\\UnstapleTest::__pest_evaluable_it_can_unstaple_a_document":13.455,"P\\Tests\\Feature\\Requests\\Documents\\ApplicationProperties\\ApplicationPropertiesTest::__pest_evaluable_it_can_add_get_update_delete_application_properties_to_a_document":1.042,"P\\Tests\\Feature\\Requests\\General\\UserManagement\\GetUsers\\GetUsersTest::__pest_evaluable_it_can_list_users":0.746,"P\\Tests\\Feature\\Requests\\Organization\\GetOrganizationsRequestTest::__pest_evaluable_it_can_list_organizations":0.504,"P\\Tests\\Feature\\Requests\\Fields\\GetFieldsRequestTest::__pest_evaluable_it_can_list_fields_for_a_file_cabinet":0.53,"P\\Tests\\Feature\\Requests\\Search\\GetSearchRequestTest::__pest_evaluable_it_can_override_search_documents_dates_filter_by_using_equal_operator":0.766,"P\\Tests\\Feature\\Requests\\Search\\GetSearchRequestTest::__pest_evaluable_it_can_search_documents_dates_filter_in_past":0.661,"P\\Tests\\Feature\\Requests\\Search\\GetSearchRequestTest::__pest_evaluable_it_can_search_documents":0.58,"P\\Tests\\Feature\\Requests\\Search\\GetSearchRequestTest::__pest_evaluable_it_can_search_documents_with_multiple_values":1.078,"P\\Tests\\Feature\\Requests\\Search\\GetSearchRequestTest::__pest_evaluable_it_can_search_documents_with_null_values":0.495,"P\\Tests\\Feature\\Requests\\Search\\GetSearchRequestTest::__pest_evaluable_it_can_override_search_documents_dates_filter_by_using_same_operator":0.617,"P\\Tests\\Feature\\Requests\\Search\\GetSearchRequestTest::__pest_evaluable_it_can_t_search_documents_by_more_than_two_dates":0.371,"P\\Tests\\Feature\\Requests\\Search\\GetSearchRequestTest::__pest_evaluable_it_can_search_documents_dates_filter_in_future":0.645,"P\\Tests\\Feature\\Requests\\Search\\GetSearchRequestTest::__pest_evaluable_it_can_t_search_documents_by_diverged_date_range":0.364,"P\\Tests\\Feature\\DocuWareTest::__pest_evaluable_it_can_create_encrypted_url_for_a_document_in_a_file_cabinet":0.424,"P\\Tests\\Feature\\DocuWareTest::__pest_evaluable_it_can_create_encrypted_url_for_a_document_in_a_basket":0.369,"P\\Tests\\Feature\\Requests\\General\\UserManagement\\GetModifyGroups\\GetAllGroupsForASpecificUserTest::__pest_evaluable_it_can_list_groups_for_a_specific_user":0.884,"P\\Tests\\Feature\\Requests\\FileCabinets\\Dialogs\\GetASpecificDialogTest::__pest_evaluable_it_can_get_a_dialog":0.555,"P\\Tests\\Feature\\Requests\\FileCabinets\\General\\GetFileCabinetInformationTest::__pest_evaluable_it_can_get_file_cabinet_information":0.63,"P\\Tests\\Feature\\DTOTest::__pest_evaluable_it_create_a_fake_organization_index":0.395,"P\\Tests\\Feature\\DTOTest::__pest_evaluable_it_create_a_fake_document":0.351,"P\\Tests\\Feature\\DTOTest::__pest_evaluable_it_create_a_fake_dialog":0.405,"P\\Tests\\Feature\\DTOTest::__pest_evaluable_it_create_a_fake_file_cabinet":0.352,"P\\Tests\\Feature\\DTOTest::__pest_evaluable_it_create_a_fake_field":0.336,"P\\Tests\\Feature\\DTOTest::__pest_evaluable_it_create_a_fake_document_field":0.34,"P\\Tests\\Feature\\DTOTest::__pest_evaluable_it_create_a_fake_table_row":0.352,"P\\Tests\\Feature\\DTOTest::__pest_evaluable_it_create_a_fake_suggestion_field":0.361,"P\\Tests\\Feature\\DTOTest::__pest_evaluable_it_create_a_fake_document_paginator":0.361,"P\\Tests\\Feature\\DTOTest::__pest_evaluable_it_create_a_fake_organization":0.346,"P\\Tests\\Unit\\DTO\\DocumentIndex\\IndexDateDTOTest::__pest_evaluable_it_create_index_date_dto":0.002,"P\\Tests\\Feature\\Requests\\General\\UserManagement\\GetModifyRoles\\GetRolesTest::__pest_evaluable_it_can_list_groups":0.494,"P\\Tests\\Feature\\Requests\\FileCabinets\\Search\\GetASpecificDocumentFromAFileCabinetTest::__pest_evaluable_it_can_show_a_document":0.666,"P\\Tests\\Feature\\Requests\\Documents\\DocumentsTrashBin\\RestoreDocumentsFromTrashBinTest::__pest_evaluable_it_can_restore_documents_in_trash":1.184,"P\\Tests\\Feature\\Requests\\Documents\\DocumentsTrashBin\\GetDocumentsFromTrashBinTest::__pest_evaluable_it_can_search_documents_dates_filter_in_past_in_trash":0.518,"P\\Tests\\Feature\\Requests\\Documents\\DocumentsTrashBin\\GetDocumentsFromTrashBinTest::__pest_evaluable_it_can_t_search_documents_by_more_than_two_dates_in_trash":0.331,"P\\Tests\\Feature\\Requests\\Documents\\DocumentsTrashBin\\GetDocumentsFromTrashBinTest::__pest_evaluable_it_can_override_search_documents_dates_filter_by_using_same_operator_in_trash":0.565,"P\\Tests\\Feature\\Requests\\Documents\\DocumentsTrashBin\\GetDocumentsFromTrashBinTest::__pest_evaluable_it_can_override_search_documents_dates_filter_by_using_equal_operator_in_trash":0.56,"P\\Tests\\Feature\\Requests\\Documents\\DocumentsTrashBin\\GetDocumentsFromTrashBinTest::__pest_evaluable_it_can_search_documents_in_trash":0.532,"P\\Tests\\Feature\\Requests\\Documents\\DocumentsTrashBin\\GetDocumentsFromTrashBinTest::__pest_evaluable_it_can_search_documents_with_null_values_in_trash":0.568,"P\\Tests\\Feature\\Requests\\Documents\\DocumentsTrashBin\\GetDocumentsFromTrashBinTest::__pest_evaluable_it_can_t_search_documents_by_diverged_date_range":0.364,"P\\Tests\\Feature\\Requests\\Documents\\DocumentsTrashBin\\GetDocumentsFromTrashBinTest::__pest_evaluable_it_can_search_documents_dates_filter_in_future_in_trash":0.51,"P\\Tests\\Feature\\Requests\\FileCabinets\\Upload\\ReplaceAPDFDocumentSectionTest::__pest_evaluable_it_can_replace_a_pdf_document_section":1.084,"P\\Tests\\Unit\\DTO\\DocumentIndex\\IndexKeywordDTOTest::__pest_evaluable_it_create_prepare_index_keyword_dto":0.001,"P\\Tests\\Feature\\Requests\\General\\UserManagement\\GetUsers\\GetUsersOfARoleTest::__pest_evaluable_it_can_list_users_of_a_role":0.649,"P\\Tests\\Feature\\DocuWareAuthenticationTest::__pest_evaluable_it_can_get_oath_responsible_identity_service":0.497,"P\\Tests\\Feature\\DocuWareAuthenticationTest::__pest_evaluable_it_throws_an_error_if_credentials_are_wrong":0.839,"P\\Tests\\Feature\\DocuWareAuthenticationTest::__pest_evaluable_it_can_authenticate_with_DocuWare_Credentials":0.48,"P\\Tests\\Feature\\DocuWareAuthenticationTest::__pest_evaluable_it_can_get_oath_identity_service_configuration":0.701,"P\\Tests\\Unit\\DTO\\DocumentIndex\\PrepareDTOTest::__pest_evaluable_it_create_prepare_makeContent_dto":0,"P\\Tests\\Feature\\Requests\\Documents\\Download\\DownloadThumbnailTest::__pest_evaluable_it_can_download_a_thumbnail":0.814,"P\\Tests\\Feature\\Requests\\Documents\\ClipUnclipStapleUnstaple\\StapleTest::__pest_evaluable_it_can_staple_2_documents":12.323,"P\\Tests\\Unit\\DTO\\DocumentIndex\\IndexDecimalDTOTest::__pest_evaluable_it_create_index_numeric_dto":0.001,"P\\Tests\\Feature\\Requests\\General\\UserManagement\\GetUsers\\GetUserByIdTest::__pest_evaluable_it_can_get_user_by_id":0.763,"P\\Tests\\Feature\\Requests\\General\\UserManagement\\GetModifyGroups\\GetGroupsTest::__pest_evaluable_it_can_list_groups":0.548,"P\\Tests\\Feature\\Requests\\Workflow\\GetDocumentWorkflowHistoryTest::__pest_evaluable_it_can_get_document_workflow_history":0.359,"P\\Tests\\Feature\\Requests\\General\\UserManagement\\GetModifyGroups\\AddRemoveUserToAGroupTest::__pest_evaluable_it_can_add_groups_to_a_user":7.947,"P\\Tests\\Feature\\Requests\\General\\UserManagement\\GetModifyGroups\\AddRemoveUserToAGroupTest::__pest_evaluable_it_can_remove_groups_to_a_user":5.958,"P\\Tests\\Feature\\Requests\\Documents\\PostDocumentRequestTest::__pest_evaluable_it_can_upload_document_without_file_name_and_file_content_and_delete_it":0.565,"P\\Tests\\Feature\\Requests\\Documents\\PostDocumentRequestTest::__pest_evaluable_it_can_upload_document_with_index_values_and_delete_it":0.633,"P\\Tests\\Unit\\DTO\\DocumentIndex\\IndexDateTimeDTOTest::__pest_evaluable_it_create_index_date_time_dto":0.001,"P\\Tests\\Core\\ArchTest::__pest_evaluable_it_will_not_use_any_debug_function":0.231,"P\\Tests\\Feature\\Requests\\Documents\\GetDocumentPreviewRequestTest::__pest_evaluable_it_can_preview_a_document_image":0.941,"P\\Tests\\Feature\\Requests\\Documents\\PutDocumentFieldsRequestTest::__pest_evaluable_it_can_update_a_document_value":0.679,"P\\Tests\\Feature\\Requests\\Documents\\PutDocumentFieldsRequestTest::__pest_evaluable_it_can_update_multiple_document_values":0.803,"P\\Tests\\Feature\\SleepTest::__pest_evaluable_sleep":0.339,"P\\Tests\\Feature\\Requests\\Documents\\ClipUnclipStapleUnstaple\\ClipTest::__pest_evaluable_it_can_clip_2_documents":6.624,"P\\Tests\\Feature\\Laravel12CacheCompatibilityTest::__pest_evaluable_cache_configuration_uses_CACHE__STORE_in_Laravel_12":0.003,"P\\Tests\\Feature\\Laravel12CacheCompatibilityTest::__pest_evaluable_cache_configuration_falls_back_to_CACHE__DRIVER_when_CACHE__STORE_is_not_set":0.425,"P\\Tests\\Feature\\Laravel12CacheCompatibilityTest::__pest_evaluable_cache_configuration_uses_DOCUWARE__CACHE__DRIVER_when_explicitly_set":0.876,"P\\Tests\\Feature\\Requests\\Documents\\Download\\DownloadDocumentTest::__pest_evaluable_it_can_download_a_document":0.705,"P\\Tests\\Feature\\Requests\\FileCabinets\\Upload\\AppendASinglePDFToADocumentTest::__pest_evaluable_it_can_replace_a_pdf_document_section":0.87,"P\\Tests\\Feature\\Requests\\General\\UserManagement\\CreateUpdateUsers\\CreateUpdateUserTest::__pest_evaluable_it_can_create_users":1.781,"P\\Tests\\Feature\\Requests\\General\\UserManagement\\CreateUpdateUsers\\CreateUpdateUserTest::__pest_evaluable_it_can_update_users":6.156,"P\\Tests\\Feature\\Requests\\FileCabinets\\Dialogs\\GetDialogsOfASpecificTypeTest::__pest_evaluable_it_can_list_dialogs_for_a_file_cabinet":0.523,"P\\Tests\\Feature\\Requests\\Documents\\ClipUnclipStapleUnstaple\\UnclipTest::__pest_evaluable_it_can_unclip_2_documents":12.165,"P\\Tests\\Feature\\Requests\\General\\UserManagement\\GetUsers\\GetUsersOfAGroupTest::__pest_evaluable_it_can_list_users_of_a_group":0.729,"P\\Tests\\Feature\\Requests\\Workflow\\GetDocumentWorkflowHistoryStepsTest::__pest_evaluable_it_can_get_document_workflow_history":0.354,"P\\Tests\\Feature\\Requests\\FileCabinets\\Search\\GetDocumentsFromAFileCabinetTest::__pest_evaluable_it_can_get_all_documents":0.876,"P\\Tests\\Feature\\Requests\\Documents\\Sections\\DeleteSectionTest::__pest_evaluable_it_can_delete_a_specific_section":0.825,"P\\Tests\\Feature\\Requests\\FileCabinets\\Upload\\AppendFilesToADataRecordTest::__pest_evaluable_it_can_attach_a_file_to_a_data_record":0.8,"P\\Tests\\Feature\\Requests\\FileCabinets\\Upload\\AppendFilesToADataRecordTest::__pest_evaluable_it_can_attach_files_to_a_data_record":0.353,"P\\Tests\\Feature\\Requests\\General\\UserManagement\\GetModifyRoles\\GetAllRolesForASpecificUserTest::__pest_evaluable_it_can_list_groups":0.78,"P\\Tests\\Feature\\Requests\\Documents\\Sections\\GetASpecificSectionTest::__pest_evaluable_it_can_get_a_specific_section":0.76,"P\\Tests\\Feature\\Requests\\Documents\\Thumbnail\\GetDocumentDownloadThumbnailRequestTest::__pest_evaluable_it_can_download_a_document_thumbnail":0.847,"P\\Tests\\Unit\\DTO\\DocumentIndex\\IndexNumericDTOTest::__pest_evaluable_it_create_index_numeric_dto":0,"P\\Tests\\Feature\\Requests\\General\\UserManagement\\GetModifyRoles\\AddRemoveUserToARoleTest::__pest_evaluable_it_can_add_roles_to_a_user":6.818,"P\\Tests\\Feature\\Requests\\General\\UserManagement\\GetModifyRoles\\AddRemoveUserToARoleTest::__pest_evaluable_it_can_remove_roles_to_a_user":5.917,"P\\Tests\\Feature\\Requests\\Documents\\Download\\DownloadSectionTest::__pest_evaluable_it_can_download_a_section":0.849,"P\\Tests\\Unit\\DTO\\DocumentIndex\\IndexTableDTOTest::__pest_evaluable_it_create_prepare_index_text_dto_using_dto":0.001,"P\\Tests\\Unit\\DTO\\DocumentIndex\\IndexTextDTOTest::__pest_evaluable_it_create_prepare_index_text_dto":0,"P\\Tests\\Unit\\DTO\\DocumentIndex\\IndexMemoDTOTest::__pest_evaluable_it_create_prepare_index_memo_dto":0,"P\\Tests\\Feature\\Requests\\Documents\\GetDocumentCountRequestTest::__pest_evaluable_it_can_get_a_total_count_of_documents":0.879,"Tests\\Unit\\DTO\\TextshotPageTest::test_it_can_create_textshot_page_with_null_language":0,"Tests\\Unit\\DTO\\TextshotPageTest::test_it_can_create_textshot_page_with_string_language":0.006,"Tests\\Unit\\DTO\\TextshotPageTest::test_it_can_create_textshot_page_with_empty_content":0,"P\\Tests\\Feature\\Laravel12CacheCompatibilityTest::__pest_evaluable_cache_configuration_uses_default_file_driver_when_no_configuration_is_set":0.361,"P\\Tests\\Feature\\Laravel12CacheCompatibilityTest::__pest_evaluable_cache_configuration_respects_the_new_CACHE__STORE_fallback_chain":0.008,"P\\Tests\\Feature\\Laravel12CacheCompatibilityTest::__pest_evaluable_environment_variable_fallback_chain_works_correctly":0.384,"P\\Tests\\Feature\\Laravel12CacheCompatibilityTest::__pest_evaluable_CACHE__STORE_takes_precedence_over_CACHE__DRIVER_when_DOCUWARE__CACHE__DRIVER_is_not_set":0.376,"P\\Tests\\Feature\\Laravel12CacheCompatibilityTest::__pest_evaluable_cache_configuration_can_be_overridden":0.037,"P\\Tests\\Feature\\Laravel12CacheCompatibilityTest::__pest_evaluable_cache_configuration_properly_loads_from_config":0.412,"P\\Tests\\Feature\\Laravel12CacheCompatibilityTest::__pest_evaluable_cache_configuration_fallback_chain_is_properly_configured":0.385}} \ No newline at end of file +{"version":"pest_3.8.4","defects":[],"times":{"P\\Tests\\Unit\\Enums\\DialogTypeTest::__pest_evaluable_it_can_try_to_create_enum_from_string_value":0.02,"P\\Tests\\Unit\\Enums\\DialogTypeTest::__pest_evaluable_it_throws_exception_for_invalid_enum_value":0,"P\\Tests\\Unit\\Enums\\DialogTypeTest::__pest_evaluable_it_can_create_enum_from_string_value":0,"P\\Tests\\Unit\\Enums\\DialogTypeTest::__pest_evaluable_it_can_get_string_value_from_enum":0,"P\\Tests\\Unit\\Enums\\DialogTypeTest::__pest_evaluable_it_has_all_expected_dialog_type_enum_cases":0,"P\\Tests\\Unit\\Enums\\ConnectionEnumTest::__pest_evaluable_it_throws_exception_for_invalid_enum_value":0,"P\\Tests\\Unit\\Enums\\ConnectionEnumTest::__pest_evaluable_it_has_all_expected_connection_enum_cases":0,"P\\Tests\\Unit\\Enums\\ConnectionEnumTest::__pest_evaluable_it_can_create_enum_from_string_value":0,"P\\Tests\\Unit\\Enums\\ConnectionEnumTest::__pest_evaluable_it_can_get_string_value_from_enum":0,"P\\Tests\\Unit\\Enums\\ConnectionEnumTest::__pest_evaluable_it_can_try_to_create_enum_from_string_value":0,"P\\Tests\\Unit\\Enums\\DocuWareFieldTypeEnumTest::__pest_evaluable_it_throws_exception_for_invalid_enum_value":0,"P\\Tests\\Unit\\Enums\\DocuWareFieldTypeEnumTest::__pest_evaluable_it_can_try_to_create_enum_from_string_value":0,"P\\Tests\\Unit\\Enums\\DocuWareFieldTypeEnumTest::__pest_evaluable_it_can_be_used_in_switch_statements":0,"P\\Tests\\Unit\\Enums\\DocuWareFieldTypeEnumTest::__pest_evaluable_it_can_create_enum_from_string_value":0,"P\\Tests\\Unit\\Enums\\DocuWareFieldTypeEnumTest::__pest_evaluable_it_can_get_string_value_from_enum":0,"P\\Tests\\Unit\\Enums\\DocuWareFieldTypeEnumTest::__pest_evaluable_it_has_all_expected_field_type_enum_cases":0}} \ No newline at end of file diff --git a/README.md b/README.md index 5e866513..a5440133 100644 --- a/README.md +++ b/README.md @@ -315,6 +315,52 @@ $connector = new DocuWareConnector( ); ``` +### Enums + +The package provides several enums to ensure type safety and consistency when working with DocuWare API values. + +#### ConnectionEnum + +Represents different connection types for DocuWare authentication: + +```php +use CodebarAg\DocuWare\Enums\ConnectionEnum; + +ConnectionEnum::WITHOUT_COOKIE; +ConnectionEnum::STATIC_COOKIE; +ConnectionEnum::DYNAMIC_COOKIE; +``` + +#### DialogType + +Represents different types of dialogs in DocuWare: + +```php +use CodebarAg\DocuWare\Enums\DialogType; + +DialogType::SEARCH; +DialogType::STORE; +DialogType::RESULT; +DialogType::INDEX; +DialogType::LIST; +DialogType::FOLDERS; +``` + +#### DocuWareFieldTypeEnum + +Represents different field types used in DocuWare document indexing: + +```php +use CodebarAg\DocuWare\Enums\DocuWareFieldTypeEnum; + +DocuWareFieldTypeEnum::STRING; +DocuWareFieldTypeEnum::INT; +DocuWareFieldTypeEnum::DECIMAL; +DocuWareFieldTypeEnum::DATE; +DocuWareFieldTypeEnum::DATETIME; +DocuWareFieldTypeEnum::TABLE; +``` + ### Available Requests The following sections provide examples for each available request type. All functionality is documented inline below with code examples. diff --git a/src/Enums/DocuWareFieldTypeEnum.php b/src/Enums/DocuWareFieldTypeEnum.php new file mode 100644 index 00000000..e3370d77 --- /dev/null +++ b/src/Enums/DocuWareFieldTypeEnum.php @@ -0,0 +1,13 @@ + $case->value, ConnectionEnum::cases()); + + expect($actualCases)->toBe($expectedCases); +}); + +it('can get string value from enum', function () { + expect(ConnectionEnum::WITHOUT_COOKIE->value)->toBe('WITHOUT_COOKIE'); + expect(ConnectionEnum::STATIC_COOKIE->value)->toBe('STATIC_COOKIE'); + expect(ConnectionEnum::DYNAMIC_COOKIE->value)->toBe('DYNAMIC_COOKIE'); +}); + +it('can create enum from string value', function () { + expect(ConnectionEnum::from('WITHOUT_COOKIE'))->toBe(ConnectionEnum::WITHOUT_COOKIE); + expect(ConnectionEnum::from('STATIC_COOKIE'))->toBe(ConnectionEnum::STATIC_COOKIE); + expect(ConnectionEnum::from('DYNAMIC_COOKIE'))->toBe(ConnectionEnum::DYNAMIC_COOKIE); +}); + +it('throws exception for invalid enum value', function () { + expect(fn () => ConnectionEnum::from('INVALID_VALUE')) + ->toThrow(ValueError::class); +}); + +it('can try to create enum from string value', function () { + expect(ConnectionEnum::tryFrom('WITHOUT_COOKIE'))->toBe(ConnectionEnum::WITHOUT_COOKIE); + expect(ConnectionEnum::tryFrom('INVALID_VALUE'))->toBeNull(); +}); diff --git a/tests/Unit/Enums/DialogTypeTest.php b/tests/Unit/Enums/DialogTypeTest.php new file mode 100644 index 00000000..7f5008b5 --- /dev/null +++ b/tests/Unit/Enums/DialogTypeTest.php @@ -0,0 +1,46 @@ + $case->value, DialogType::cases()); + + expect($actualCases)->toBe($expectedCases); +}); + +it('can get string value from enum', function () { + expect(DialogType::SEARCH->value)->toBe('Search'); + expect(DialogType::STORE->value)->toBe('Store'); + expect(DialogType::RESULT->value)->toBe('Result'); + expect(DialogType::INDEX->value)->toBe('Index'); + expect(DialogType::LIST->value)->toBe('List'); + expect(DialogType::FOLDERS->value)->toBe('Folders'); +}); + +it('can create enum from string value', function () { + expect(DialogType::from('Search'))->toBe(DialogType::SEARCH); + expect(DialogType::from('Store'))->toBe(DialogType::STORE); + expect(DialogType::from('Result'))->toBe(DialogType::RESULT); + expect(DialogType::from('Index'))->toBe(DialogType::INDEX); + expect(DialogType::from('List'))->toBe(DialogType::LIST); + expect(DialogType::from('Folders'))->toBe(DialogType::FOLDERS); +}); + +it('throws exception for invalid enum value', function () { + expect(fn () => DialogType::from('InvalidType')) + ->toThrow(ValueError::class); +}); + +it('can try to create enum from string value', function () { + expect(DialogType::tryFrom('Search'))->toBe(DialogType::SEARCH); + expect(DialogType::tryFrom('InvalidType'))->toBeNull(); +}); diff --git a/tests/Unit/Enums/DocuWareFieldTypeEnumTest.php b/tests/Unit/Enums/DocuWareFieldTypeEnumTest.php new file mode 100644 index 00000000..cadd5f2c --- /dev/null +++ b/tests/Unit/Enums/DocuWareFieldTypeEnumTest.php @@ -0,0 +1,61 @@ + $case->value, DocuWareFieldTypeEnum::cases()); + + expect($actualCases)->toBe($expectedCases); +}); + +it('can get string value from enum', function () { + expect(DocuWareFieldTypeEnum::STRING->value)->toBe('String'); + expect(DocuWareFieldTypeEnum::INT->value)->toBe('Int'); + expect(DocuWareFieldTypeEnum::DECIMAL->value)->toBe('Decimal'); + expect(DocuWareFieldTypeEnum::DATE->value)->toBe('Date'); + expect(DocuWareFieldTypeEnum::DATETIME->value)->toBe('DateTime'); + expect(DocuWareFieldTypeEnum::TABLE->value)->toBe('Table'); +}); + +it('can create enum from string value', function () { + expect(DocuWareFieldTypeEnum::from('String'))->toBe(DocuWareFieldTypeEnum::STRING); + expect(DocuWareFieldTypeEnum::from('Int'))->toBe(DocuWareFieldTypeEnum::INT); + expect(DocuWareFieldTypeEnum::from('Decimal'))->toBe(DocuWareFieldTypeEnum::DECIMAL); + expect(DocuWareFieldTypeEnum::from('Date'))->toBe(DocuWareFieldTypeEnum::DATE); + expect(DocuWareFieldTypeEnum::from('DateTime'))->toBe(DocuWareFieldTypeEnum::DATETIME); + expect(DocuWareFieldTypeEnum::from('Table'))->toBe(DocuWareFieldTypeEnum::TABLE); +}); + +it('throws exception for invalid enum value', function () { + expect(fn () => DocuWareFieldTypeEnum::from('InvalidType')) + ->toThrow(ValueError::class); +}); + +it('can try to create enum from string value', function () { + expect(DocuWareFieldTypeEnum::tryFrom('String'))->toBe(DocuWareFieldTypeEnum::STRING); + expect(DocuWareFieldTypeEnum::tryFrom('InvalidType'))->toBeNull(); +}); + +it('can be used in switch statements', function () { + $fieldType = DocuWareFieldTypeEnum::STRING; + + $result = match ($fieldType) { + DocuWareFieldTypeEnum::STRING => 'text field', + DocuWareFieldTypeEnum::INT => 'numeric field', + DocuWareFieldTypeEnum::DECIMAL => 'decimal field', + DocuWareFieldTypeEnum::DATE => 'date field', + DocuWareFieldTypeEnum::DATETIME => 'datetime field', + DocuWareFieldTypeEnum::TABLE => 'table field', + }; + + expect($result)->toBe('text field'); +}); From b96f386aefa8b73d390f79986f5f3c50439223e1 Mon Sep 17 00:00:00 2001 From: Sebastian Fix Date: Fri, 17 Oct 2025 16:03:58 +0200 Subject: [PATCH 03/29] Removed phpunit cache --- .gitignore | 1 + .phpunit.cache/test-results | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 .phpunit.cache/test-results diff --git a/.gitignore b/.gitignore index 2536343a..0d3a8e74 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ vendor node_modules .phpactor.json build +.phpunit.cache ## Annotations src/DTO/Documents/AnnotationsStamps/* diff --git a/.phpunit.cache/test-results b/.phpunit.cache/test-results deleted file mode 100644 index 7c94d9dd..00000000 --- a/.phpunit.cache/test-results +++ /dev/null @@ -1 +0,0 @@ -{"version":"pest_3.8.4","defects":[],"times":{"P\\Tests\\Unit\\Enums\\DialogTypeTest::__pest_evaluable_it_can_try_to_create_enum_from_string_value":0.02,"P\\Tests\\Unit\\Enums\\DialogTypeTest::__pest_evaluable_it_throws_exception_for_invalid_enum_value":0,"P\\Tests\\Unit\\Enums\\DialogTypeTest::__pest_evaluable_it_can_create_enum_from_string_value":0,"P\\Tests\\Unit\\Enums\\DialogTypeTest::__pest_evaluable_it_can_get_string_value_from_enum":0,"P\\Tests\\Unit\\Enums\\DialogTypeTest::__pest_evaluable_it_has_all_expected_dialog_type_enum_cases":0,"P\\Tests\\Unit\\Enums\\ConnectionEnumTest::__pest_evaluable_it_throws_exception_for_invalid_enum_value":0,"P\\Tests\\Unit\\Enums\\ConnectionEnumTest::__pest_evaluable_it_has_all_expected_connection_enum_cases":0,"P\\Tests\\Unit\\Enums\\ConnectionEnumTest::__pest_evaluable_it_can_create_enum_from_string_value":0,"P\\Tests\\Unit\\Enums\\ConnectionEnumTest::__pest_evaluable_it_can_get_string_value_from_enum":0,"P\\Tests\\Unit\\Enums\\ConnectionEnumTest::__pest_evaluable_it_can_try_to_create_enum_from_string_value":0,"P\\Tests\\Unit\\Enums\\DocuWareFieldTypeEnumTest::__pest_evaluable_it_throws_exception_for_invalid_enum_value":0,"P\\Tests\\Unit\\Enums\\DocuWareFieldTypeEnumTest::__pest_evaluable_it_can_try_to_create_enum_from_string_value":0,"P\\Tests\\Unit\\Enums\\DocuWareFieldTypeEnumTest::__pest_evaluable_it_can_be_used_in_switch_statements":0,"P\\Tests\\Unit\\Enums\\DocuWareFieldTypeEnumTest::__pest_evaluable_it_can_create_enum_from_string_value":0,"P\\Tests\\Unit\\Enums\\DocuWareFieldTypeEnumTest::__pest_evaluable_it_can_get_string_value_from_enum":0,"P\\Tests\\Unit\\Enums\\DocuWareFieldTypeEnumTest::__pest_evaluable_it_has_all_expected_field_type_enum_cases":0}} \ No newline at end of file From 30b8d829046fac0d438d8ebd3137dec39a909bfb Mon Sep 17 00:00:00 2001 From: Sebastian Fix Date: Fri, 17 Oct 2025 16:08:23 +0200 Subject: [PATCH 04/29] Resolved #188 --- .../UserManagement/GetUsers/OutOfOffice.php | 2 -- .../ModifyDocuments/TransferDocument.php | 31 ++++++++++++++----- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/src/DTO/General/UserManagement/GetUsers/OutOfOffice.php b/src/DTO/General/UserManagement/GetUsers/OutOfOffice.php index 2a5a356f..eab35494 100644 --- a/src/DTO/General/UserManagement/GetUsers/OutOfOffice.php +++ b/src/DTO/General/UserManagement/GetUsers/OutOfOffice.php @@ -12,14 +12,12 @@ public static function fromJson(array $data): self { if ($startDateTime = Arr::get($data, 'StartDateTime')) { $startDateTime = Str::of($startDateTime)->after('(')->before(')'); - // Extract milliseconds part (ignore optional timezone offset like +0000) $milliseconds = (int) (string) $startDateTime; $startDateTime = Carbon::createFromTimestampMs($milliseconds, 'UTC'); } if ($endDateTime = Arr::get($data, 'EndDateTime')) { $endDateTime = Str::of($endDateTime)->after('(')->before(')'); - // Extract milliseconds part (ignore optional timezone offset like +0000) $milliseconds = (int) (string) $endDateTime; $endDateTime = Carbon::createFromTimestampMs($milliseconds, 'UTC'); } diff --git a/src/Requests/Documents/ModifyDocuments/TransferDocument.php b/src/Requests/Documents/ModifyDocuments/TransferDocument.php index 3431ec5d..2ead5f51 100644 --- a/src/Requests/Documents/ModifyDocuments/TransferDocument.php +++ b/src/Requests/Documents/ModifyDocuments/TransferDocument.php @@ -2,7 +2,9 @@ namespace CodebarAg\DocuWare\Requests\Documents\ModifyDocuments; +use CodebarAg\DocuWare\DTO\Documents\DocumentIndex\PrepareDTO; use CodebarAg\DocuWare\Responses\Documents\ModifyDocuments\TransferDocumentResponse; +use Illuminate\Support\Collection; use Saloon\Contracts\Body\HasBody; use Saloon\Enums\Method; use Saloon\Http\Request; @@ -18,9 +20,12 @@ class TransferDocument extends Request implements HasBody public function __construct( protected readonly string $fileCabinetId, protected readonly string $destinationFileCabinetId, - protected readonly string $storeDialogId, protected readonly string $documentId, - protected readonly array $fields = [], + protected readonly ?string $storeDialogId = null, + protected readonly ?Collection $fields = null, + protected readonly bool $keepSource = false, + protected readonly bool $fillIntellix = false, + protected readonly bool $useDefaultDialog = false, ) {} public function resolveEndpoint(): string @@ -39,18 +44,30 @@ protected function defaultHeaders(): array protected function defaultBody(): array { - return [ + $body = [ 'SourceFileCabinetId' => $this->fileCabinetId, 'Documents' => [ [ 'Id' => $this->documentId, - 'Fields' => $this->fields, ], ], - 'KeepSource' => false, - 'FillIntellix' => false, - 'UseDefaultDialog' => false, + 'KeepSource' => $this->keepSource, + 'FillIntellix' => $this->fillIntellix, + 'UseDefaultDialog' => $this->useDefaultDialog, ]; + + // Add storeDialogId if provided + if ($this->storeDialogId) { + $body['StoreDialogId'] = $this->storeDialogId; + } + + // Add fields if provided + if ($this->fields) { + $fieldData = PrepareDTO::makeField($this->fields); + $body['Documents'][0]['Fields'] = $fieldData['Field']; + } + + return $body; } public function createDtoFromResponse(Response $response): bool From fd4e1cfa35d6e3ef16d5c8e747ff5fc3434601d1 Mon Sep 17 00:00:00 2001 From: Sebastian Fix Date: Fri, 17 Oct 2025 16:09:00 +0200 Subject: [PATCH 05/29] wip --- src/Requests/Documents/ModifyDocuments/TransferDocument.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Requests/Documents/ModifyDocuments/TransferDocument.php b/src/Requests/Documents/ModifyDocuments/TransferDocument.php index 2ead5f51..4ac99aec 100644 --- a/src/Requests/Documents/ModifyDocuments/TransferDocument.php +++ b/src/Requests/Documents/ModifyDocuments/TransferDocument.php @@ -56,12 +56,10 @@ protected function defaultBody(): array 'UseDefaultDialog' => $this->useDefaultDialog, ]; - // Add storeDialogId if provided if ($this->storeDialogId) { $body['StoreDialogId'] = $this->storeDialogId; } - // Add fields if provided if ($this->fields) { $fieldData = PrepareDTO::makeField($this->fields); $body['Documents'][0]['Fields'] = $fieldData['Field']; From d8c2a1af7e5b6342a7df169c38e1637fa67255fa Mon Sep 17 00:00:00 2001 From: Sebastian Fix Date: Fri, 17 Oct 2025 16:10:29 +0200 Subject: [PATCH 06/29] Minor code improvements --- src/DTO/General/UserManagement/GetUsers/OutOfOffice.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/DTO/General/UserManagement/GetUsers/OutOfOffice.php b/src/DTO/General/UserManagement/GetUsers/OutOfOffice.php index eab35494..282ae282 100644 --- a/src/DTO/General/UserManagement/GetUsers/OutOfOffice.php +++ b/src/DTO/General/UserManagement/GetUsers/OutOfOffice.php @@ -10,13 +10,17 @@ final class OutOfOffice { public static function fromJson(array $data): self { - if ($startDateTime = Arr::get($data, 'StartDateTime')) { + $startDateTime = Arr::get($data, 'StartDateTime'); + + if (filled($startDateTime)) { $startDateTime = Str::of($startDateTime)->after('(')->before(')'); $milliseconds = (int) (string) $startDateTime; $startDateTime = Carbon::createFromTimestampMs($milliseconds, 'UTC'); } - if ($endDateTime = Arr::get($data, 'EndDateTime')) { + $endDateTime = Arr::get($data, 'EndDateTime'); + + if (filled($endDateTime)) { $endDateTime = Str::of($endDateTime)->after('(')->before(')'); $milliseconds = (int) (string) $endDateTime; $endDateTime = Carbon::createFromTimestampMs($milliseconds, 'UTC'); From 56b872e7bca4849ba3e590384c2c7fcd1e1cc60e Mon Sep 17 00:00:00 2001 From: Sebastian Fix Date: Fri, 17 Oct 2025 16:17:28 +0200 Subject: [PATCH 07/29] Improved TransferDocument Request --- .../UserManagement/GetUsers/OutOfOffice.php | 8 +++++--- .../ModifyDocuments/TransferDocument.php | 19 +++++++++---------- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/DTO/General/UserManagement/GetUsers/OutOfOffice.php b/src/DTO/General/UserManagement/GetUsers/OutOfOffice.php index 282ae282..8aeb77a4 100644 --- a/src/DTO/General/UserManagement/GetUsers/OutOfOffice.php +++ b/src/DTO/General/UserManagement/GetUsers/OutOfOffice.php @@ -12,10 +12,12 @@ public static function fromJson(array $data): self { $startDateTime = Arr::get($data, 'StartDateTime'); + $timeZone = config('app.timezone', 'UTC'); + if (filled($startDateTime)) { $startDateTime = Str::of($startDateTime)->after('(')->before(')'); $milliseconds = (int) (string) $startDateTime; - $startDateTime = Carbon::createFromTimestampMs($milliseconds, 'UTC'); + $startDateTime = Carbon::createFromTimestampMs($milliseconds, $timeZone); } $endDateTime = Arr::get($data, 'EndDateTime'); @@ -23,7 +25,7 @@ public static function fromJson(array $data): self if (filled($endDateTime)) { $endDateTime = Str::of($endDateTime)->after('(')->before(')'); $milliseconds = (int) (string) $endDateTime; - $endDateTime = Carbon::createFromTimestampMs($milliseconds, 'UTC'); + $endDateTime = Carbon::createFromTimestampMs($milliseconds, $timeZone); } return new self( @@ -31,7 +33,7 @@ public static function fromJson(array $data): self startDateTime: $startDateTime, startDateTimeSpecified: Arr::get($data, 'StartDateTimeSpecified'), endDateTime: $endDateTime, - endDateTimeSpecified: Arr::get($data, key: 'EndDateTimeSpecified'), + endDateTimeSpecified: Arr::get($data, 'EndDateTimeSpecified'), ); } diff --git a/src/Requests/Documents/ModifyDocuments/TransferDocument.php b/src/Requests/Documents/ModifyDocuments/TransferDocument.php index 4ac99aec..b80a2a96 100644 --- a/src/Requests/Documents/ModifyDocuments/TransferDocument.php +++ b/src/Requests/Documents/ModifyDocuments/TransferDocument.php @@ -2,6 +2,7 @@ namespace CodebarAg\DocuWare\Requests\Documents\ModifyDocuments; +use Arr; use CodebarAg\DocuWare\DTO\Documents\DocumentIndex\PrepareDTO; use CodebarAg\DocuWare\Responses\Documents\ModifyDocuments\TransferDocumentResponse; use Illuminate\Support\Collection; @@ -30,7 +31,13 @@ public function __construct( public function resolveEndpoint(): string { - return '/FileCabinets/'.$this->destinationFileCabinetId.'/Task/Transfer'; + $endpoint = '/FileCabinets/'.$this->destinationFileCabinetId.'/Task/Transfer'; + + if ($this->storeDialogId) { + $endpoint .= '?StoreDialogId='.$this->storeDialogId; + } + + return $endpoint; } protected function defaultHeaders(): array @@ -49,6 +56,7 @@ protected function defaultBody(): array 'Documents' => [ [ 'Id' => $this->documentId, + 'Fields' => $this->fields ? Arr::get(PrepareDTO::makeField($this->fields), 'Field') : null, ], ], 'KeepSource' => $this->keepSource, @@ -56,15 +64,6 @@ protected function defaultBody(): array 'UseDefaultDialog' => $this->useDefaultDialog, ]; - if ($this->storeDialogId) { - $body['StoreDialogId'] = $this->storeDialogId; - } - - if ($this->fields) { - $fieldData = PrepareDTO::makeField($this->fields); - $body['Documents'][0]['Fields'] = $fieldData['Field']; - } - return $body; } From 9de480befc4c7f45be8c9bf4edfd812033e413a5 Mon Sep 17 00:00:00 2001 From: Sebastian Fix Date: Fri, 20 Mar 2026 06:58:06 +0100 Subject: [PATCH 08/29] Security updates --- .github/dependabot.yml | 40 ++++++++++++++++++++++++------ CONTRIBUTING.md | 55 ++++++++++++++++++++++++++++++++++++++++++ SECURITY.md | 3 +++ 3 files changed, 91 insertions(+), 7 deletions(-) create mode 100644 CONTRIBUTING.md create mode 100644 SECURITY.md diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 30c8a493..9ac6a658 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,12 +1,38 @@ -# Please see the documentation for all configuration options: -# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates - version: 2 updates: - - package-ecosystem: "github-actions" directory: "/" schedule: - interval: "weekly" - labels: - - "dependencies" \ No newline at end of file + interval: "daily" + ignore: + - dependency-name: "*" + update-types: [ "version-update:semver-major" ] + groups: + minor-and-patch: + update-types: [ "minor", "patch" ] + + - package-ecosystem: "npm" + directory: "/" + schedule: + interval: "daily" + allow: + - dependency-type: "direct" + ignore: + - dependency-name: "*" + update-types: [ "version-update:semver-major" ] + groups: + minor-and-patch: + update-types: [ "minor", "patch" ] + + - package-ecosystem: "composer" + directory: "/" + schedule: + interval: "daily" + allow: + - dependency-type: "direct" + ignore: + - dependency-name: "*" + update-types: [ "version-update:semver-major" ] + groups: + minor-and-patch: + update-types: [ "minor", "patch" ] diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..b4ae1c4a --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,55 @@ +# Contributing + +Contributions are **welcome** and will be fully **credited**. + +Please read and understand the contribution guide before creating an issue or pull request. + +## Etiquette + +This project is open source, and as such, the maintainers give their free time to build and maintain the source code +held within. They make the code freely available in the hope that it will be of use to other developers. It would be +extremely unfair for them to suffer abuse or anger for their hard work. + +Please be considerate towards maintainers when raising issues or presenting pull requests. Let's show the +world that developers are civilized and selfless people. + +It's the duty of the maintainer to ensure that all submissions to the project are of sufficient +quality to benefit the project. Many developers have different skillsets, strengths, and weaknesses. Respect the maintainer's decision, and do not be upset or abusive if your submission is not used. + +## Viability + +When requesting or submitting new features, first consider whether it might be useful to others. Open +source projects are used by many developers, who may have entirely different needs to your own. Think about +whether or not your feature is likely to be used by other users of the project. + +## Procedure + +Before filing an issue: + +- Attempt to replicate the problem, to ensure that it wasn't a coincidental incident. +- Check to make sure your feature suggestion isn't already present within the project. +- Check the pull requests tab to ensure that the bug doesn't have a fix in progress. +- Check the pull requests tab to ensure that the feature isn't already in progress. + +Before submitting a pull request: + +- Check the codebase to ensure that your feature doesn't already exist. +- Check the pull requests to ensure that another person hasn't already submitted the feature or fix. + +## Requirements + +If the project maintainer has any additional requirements, you will find them listed here. + +- **[PSR-2 Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)** - The easiest way to apply the conventions is to install [PHP Code Sniffer](https://pear.php.net/package/PHP_CodeSniffer). + +- **Add tests!** - Your patch won't be accepted if it doesn't have tests. + +- **Document any change in behaviour** - Make sure the `README.md` and any other relevant documentation are kept up-to-date. + +- **Consider our release cycle** - We try to follow [SemVer v2.0.0](https://semver.org/). Randomly breaking public APIs is not an option. + +- **One pull request per feature** - If you want to do more than one thing, send multiple pull requests. + +- **Send coherent history** - Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please [squash them](https://www.git-scm.com/book/en/v2/Git-Tools-Rewriting-History#Changing-Multiple-Commit-Messages) before submitting. + +**Happy coding**! diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000..dd923a21 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,3 @@ +# Security Policy + +If you discover any security related issues, please email info@codebar.ch instead of using the issue tracker. From ddae347c38ccd4dd7dd76ed68fb4ced7669c8657 Mon Sep 17 00:00:00 2001 From: Sebastian Fix Date: Fri, 20 Mar 2026 07:39:21 +0100 Subject: [PATCH 09/29] Optimized Repository Skeleton --- .github/workflows/dependabot-auto-merge.yml | 2 +- .github/workflows/dependency-review.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/dependabot-auto-merge.yml b/.github/workflows/dependabot-auto-merge.yml index a3368d2d..5bfa1ad9 100644 --- a/.github/workflows/dependabot-auto-merge.yml +++ b/.github/workflows/dependabot-auto-merge.yml @@ -13,7 +13,7 @@ jobs: - name: Dependabot metadata id: metadata - uses: dependabot/fetch-metadata@v2.4.0 + uses: dependabot/fetch-metadata@v2.5.0 with: github-token: "${{ secrets.GITHUB_TOKEN }}" diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index 046e9c88..3ea91b44 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -15,6 +15,6 @@ jobs: runs-on: ubuntu-latest steps: - name: 'Checkout Repository' - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: 'Dependency Review' uses: actions/dependency-review-action@v4 From 72a9de08f2637f057b1b680fee7a5e5310dba2ab Mon Sep 17 00:00:00 2001 From: Sebastian Fix Date: Fri, 20 Mar 2026 08:23:42 +0100 Subject: [PATCH 10/29] chore: sync Laravel Pint workflow from template --- .github/workflows/fix-php-code-style-issues.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/fix-php-code-style-issues.yml b/.github/workflows/fix-php-code-style-issues.yml index 768197d9..9ed9047d 100644 --- a/.github/workflows/fix-php-code-style-issues.yml +++ b/.github/workflows/fix-php-code-style-issues.yml @@ -2,20 +2,21 @@ name: Fix PHP code style issues on: [push] +permissions: + contents: write + jobs: php-code-styling: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v5 - with: - ref: ${{ github.head_ref }} + uses: actions/checkout@v6.0.2 - name: Fix PHP code style issues uses: aglipanci/laravel-pint-action@2.6 - name: Commit changes - uses: stefanzweifel/git-auto-commit-action@v7 + uses: stefanzweifel/git-auto-commit-action@v6.0.1 with: commit_message: Fix styling From 81ff18048a9549404209964d4f2063a371815acc Mon Sep 17 00:00:00 2001 From: Sebastian Fix Date: Fri, 20 Mar 2026 08:34:56 +0100 Subject: [PATCH 11/29] chore: sync CONTRIBUTING.md from template --- .github/CONTRIBUTING.md | 2 +- CONTRIBUTING.md | 55 ----------------------------------------- 2 files changed, 1 insertion(+), 56 deletions(-) delete mode 100644 CONTRIBUTING.md diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index b4ae1c4a..3f687e93 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -40,7 +40,7 @@ Before submitting a pull request: If the project maintainer has any additional requirements, you will find them listed here. -- **[PSR-2 Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)** - The easiest way to apply the conventions is to install [PHP Code Sniffer](https://pear.php.net/package/PHP_CodeSniffer). +- **[Laravel Pint](https://github.com/laravel/pint)** - PHP style is enforced with Pint (PSR-12–oriented, via PHP-CS-Fixer). Run `composer install`, then `./vendor/bin/pint` before pushing (`composer run format` instead if your package defines that script). The **Fix PHP code style issues** GitHub Action also runs on push and may commit formatting fixes. - **Add tests!** - Your patch won't be accepted if it doesn't have tests. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index b4ae1c4a..00000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,55 +0,0 @@ -# Contributing - -Contributions are **welcome** and will be fully **credited**. - -Please read and understand the contribution guide before creating an issue or pull request. - -## Etiquette - -This project is open source, and as such, the maintainers give their free time to build and maintain the source code -held within. They make the code freely available in the hope that it will be of use to other developers. It would be -extremely unfair for them to suffer abuse or anger for their hard work. - -Please be considerate towards maintainers when raising issues or presenting pull requests. Let's show the -world that developers are civilized and selfless people. - -It's the duty of the maintainer to ensure that all submissions to the project are of sufficient -quality to benefit the project. Many developers have different skillsets, strengths, and weaknesses. Respect the maintainer's decision, and do not be upset or abusive if your submission is not used. - -## Viability - -When requesting or submitting new features, first consider whether it might be useful to others. Open -source projects are used by many developers, who may have entirely different needs to your own. Think about -whether or not your feature is likely to be used by other users of the project. - -## Procedure - -Before filing an issue: - -- Attempt to replicate the problem, to ensure that it wasn't a coincidental incident. -- Check to make sure your feature suggestion isn't already present within the project. -- Check the pull requests tab to ensure that the bug doesn't have a fix in progress. -- Check the pull requests tab to ensure that the feature isn't already in progress. - -Before submitting a pull request: - -- Check the codebase to ensure that your feature doesn't already exist. -- Check the pull requests to ensure that another person hasn't already submitted the feature or fix. - -## Requirements - -If the project maintainer has any additional requirements, you will find them listed here. - -- **[PSR-2 Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)** - The easiest way to apply the conventions is to install [PHP Code Sniffer](https://pear.php.net/package/PHP_CodeSniffer). - -- **Add tests!** - Your patch won't be accepted if it doesn't have tests. - -- **Document any change in behaviour** - Make sure the `README.md` and any other relevant documentation are kept up-to-date. - -- **Consider our release cycle** - We try to follow [SemVer v2.0.0](https://semver.org/). Randomly breaking public APIs is not an option. - -- **One pull request per feature** - If you want to do more than one thing, send multiple pull requests. - -- **Send coherent history** - Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please [squash them](https://www.git-scm.com/book/en/v2/Git-Tools-Rewriting-History#Changing-Multiple-Commit-Messages) before submitting. - -**Happy coding**! From 928c56ccdb568fe2f47c4e99b90651d44579257c Mon Sep 17 00:00:00 2001 From: Sebastian Fix Date: Fri, 20 Mar 2026 08:34:57 +0100 Subject: [PATCH 12/29] chore: sync security policy from template --- SECURITY.md | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 SECURITY.md diff --git a/SECURITY.md b/SECURITY.md deleted file mode 100644 index dd923a21..00000000 --- a/SECURITY.md +++ /dev/null @@ -1,3 +0,0 @@ -# Security Policy - -If you discover any security related issues, please email info@codebar.ch instead of using the issue tracker. From 61ccea0207fe090ab89e964675e6c0a9e2e4187c Mon Sep 17 00:00:00 2001 From: Sebastian Fix Date: Sat, 21 Mar 2026 07:58:03 +0100 Subject: [PATCH 13/29] chore: sync PHPStan workflow from template --- .github/workflows/phpstan.yml | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/.github/workflows/phpstan.yml b/.github/workflows/phpstan.yml index 4fcc1697..10e8b37c 100644 --- a/.github/workflows/phpstan.yml +++ b/.github/workflows/phpstan.yml @@ -1,13 +1,33 @@ name: PHPStan -on: [push] +on: + push: + paths: + - '**/*.php' + - 'phpstan.neon.dist' + - 'phpstan-baseline.neon' + - 'composer.json' + - 'composer.lock' + pull_request: + paths: + - '**/*.php' + - 'phpstan.neon.dist' + - 'phpstan-baseline.neon' + - 'composer.json' + - 'composer.lock' + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true jobs: phpstan: name: phpstan runs-on: ubuntu-latest + timeout-minutes: 5 steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6.0.2 - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -16,7 +36,7 @@ jobs: coverage: none - name: Install composer dependencies - uses: ramsey/composer-install@v3 + uses: ramsey/composer-install@v4 - name: Run PHPStan run: ./vendor/bin/phpstan --error-format=github From 6661fd2bfb4d3972ada5f8cc473613a43faf445e Mon Sep 17 00:00:00 2001 From: Sebastian Fix Date: Sat, 21 Mar 2026 08:07:15 +0100 Subject: [PATCH 14/29] chore: sync Laravel Pint workflow from template --- .github/workflows/fix-php-code-style-issues.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/fix-php-code-style-issues.yml b/.github/workflows/fix-php-code-style-issues.yml index 9ed9047d..1c4e562f 100644 --- a/.github/workflows/fix-php-code-style-issues.yml +++ b/.github/workflows/fix-php-code-style-issues.yml @@ -17,6 +17,6 @@ jobs: uses: aglipanci/laravel-pint-action@2.6 - name: Commit changes - uses: stefanzweifel/git-auto-commit-action@v6.0.1 + uses: stefanzweifel/git-auto-commit-action@v7 with: commit_message: Fix styling From 8720eafb1c408f09e8a8b56bd911ebb31bf118ba Mon Sep 17 00:00:00 2001 From: Sebastian Fix Date: Sat, 21 Mar 2026 08:09:44 +0100 Subject: [PATCH 15/29] chore: sync Dependabot config and workflows from template --- .github/workflows/dependency-review.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index 3ea91b44..b45940f0 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -15,6 +15,6 @@ jobs: runs-on: ubuntu-latest steps: - name: 'Checkout Repository' - uses: actions/checkout@v6 + uses: actions/checkout@v6.0.2 - name: 'Dependency Review' - uses: actions/dependency-review-action@v4 + uses: actions/dependency-review-action@v4.9.0 From 6ca012bfcdd8bb3f6de665834c826bf2793c8c28 Mon Sep 17 00:00:00 2001 From: Sebastian Fix Date: Sat, 21 Mar 2026 08:09:47 +0100 Subject: [PATCH 16/29] chore: sync Laravel Pint workflow from template --- .github/workflows/fix-php-code-style-issues.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/fix-php-code-style-issues.yml b/.github/workflows/fix-php-code-style-issues.yml index 1c4e562f..41e61934 100644 --- a/.github/workflows/fix-php-code-style-issues.yml +++ b/.github/workflows/fix-php-code-style-issues.yml @@ -17,6 +17,6 @@ jobs: uses: aglipanci/laravel-pint-action@2.6 - name: Commit changes - uses: stefanzweifel/git-auto-commit-action@v7 + uses: stefanzweifel/git-auto-commit-action@v7.1.0 with: commit_message: Fix styling From 8a79d8b0cf4da4dd699363ba2175176f3ec4955e Mon Sep 17 00:00:00 2001 From: Sebastian Fix Date: Sat, 21 Mar 2026 08:09:54 +0100 Subject: [PATCH 17/29] chore: sync PHPStan workflow from template --- .github/workflows/phpstan.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/phpstan.yml b/.github/workflows/phpstan.yml index 10e8b37c..18023123 100644 --- a/.github/workflows/phpstan.yml +++ b/.github/workflows/phpstan.yml @@ -30,13 +30,13 @@ jobs: - uses: actions/checkout@v6.0.2 - name: Setup PHP - uses: shivammathur/setup-php@v2 + uses: shivammathur/setup-php@2.37.0 with: php-version: '8.4' coverage: none - name: Install composer dependencies - uses: ramsey/composer-install@v4 + uses: ramsey/composer-install@4.0.0 - name: Run PHPStan run: ./vendor/bin/phpstan --error-format=github From b5271921a9184a74ddf64c8a49f9e7e6bbb919dc Mon Sep 17 00:00:00 2001 From: Sebastian Fix Date: Sat, 21 Mar 2026 08:09:55 +0100 Subject: [PATCH 18/29] chore: sync release workflow from template --- .github/workflows/release.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a4b057b7..c95e4148 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -10,12 +10,13 @@ jobs: release: name: Release runs-on: ubuntu-latest + timeout-minutes: 10 steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6.0.2 with: fetch-depth: '0' - name: Bump version and push tag - uses: anothrNick/github-tag-action@master + uses: anothrNick/github-tag-action@1.75.0 env: GITHUB_TOKEN: ${{ secrets.MY_GITHUB_TOKEN }} WITH_V: true From aff5617532fcc1e036d680c2465f9327fc73e937 Mon Sep 17 00:00:00 2001 From: Sebastian Fix Date: Sat, 21 Mar 2026 08:50:33 +0100 Subject: [PATCH 19/29] chore: sync CONTRIBUTING.md from template --- CONTRIBUTING.md | 55 ------------------------------------------------- 1 file changed, 55 deletions(-) delete mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index b4ae1c4a..00000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,55 +0,0 @@ -# Contributing - -Contributions are **welcome** and will be fully **credited**. - -Please read and understand the contribution guide before creating an issue or pull request. - -## Etiquette - -This project is open source, and as such, the maintainers give their free time to build and maintain the source code -held within. They make the code freely available in the hope that it will be of use to other developers. It would be -extremely unfair for them to suffer abuse or anger for their hard work. - -Please be considerate towards maintainers when raising issues or presenting pull requests. Let's show the -world that developers are civilized and selfless people. - -It's the duty of the maintainer to ensure that all submissions to the project are of sufficient -quality to benefit the project. Many developers have different skillsets, strengths, and weaknesses. Respect the maintainer's decision, and do not be upset or abusive if your submission is not used. - -## Viability - -When requesting or submitting new features, first consider whether it might be useful to others. Open -source projects are used by many developers, who may have entirely different needs to your own. Think about -whether or not your feature is likely to be used by other users of the project. - -## Procedure - -Before filing an issue: - -- Attempt to replicate the problem, to ensure that it wasn't a coincidental incident. -- Check to make sure your feature suggestion isn't already present within the project. -- Check the pull requests tab to ensure that the bug doesn't have a fix in progress. -- Check the pull requests tab to ensure that the feature isn't already in progress. - -Before submitting a pull request: - -- Check the codebase to ensure that your feature doesn't already exist. -- Check the pull requests to ensure that another person hasn't already submitted the feature or fix. - -## Requirements - -If the project maintainer has any additional requirements, you will find them listed here. - -- **[PSR-2 Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)** - The easiest way to apply the conventions is to install [PHP Code Sniffer](https://pear.php.net/package/PHP_CodeSniffer). - -- **Add tests!** - Your patch won't be accepted if it doesn't have tests. - -- **Document any change in behaviour** - Make sure the `README.md` and any other relevant documentation are kept up-to-date. - -- **Consider our release cycle** - We try to follow [SemVer v2.0.0](https://semver.org/). Randomly breaking public APIs is not an option. - -- **One pull request per feature** - If you want to do more than one thing, send multiple pull requests. - -- **Send coherent history** - Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please [squash them](https://www.git-scm.com/book/en/v2/Git-Tools-Rewriting-History#Changing-Multiple-Commit-Messages) before submitting. - -**Happy coding**! From 4fe66131d239c78955313dd37b46339571f8a47c Mon Sep 17 00:00:00 2001 From: Sebastian Fix Date: Sat, 21 Mar 2026 08:50:34 +0100 Subject: [PATCH 20/29] chore: sync security policy from template --- SECURITY.md | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 SECURITY.md diff --git a/SECURITY.md b/SECURITY.md deleted file mode 100644 index dd923a21..00000000 --- a/SECURITY.md +++ /dev/null @@ -1,3 +0,0 @@ -# Security Policy - -If you discover any security related issues, please email info@codebar.ch instead of using the issue tracker. From 43984b58f12f571b412d5100186d7c62adbe4ff2 Mon Sep 17 00:00:00 2001 From: Sebastian Fix Date: Thu, 26 Mar 2026 06:59:25 +0100 Subject: [PATCH 21/29] Optimized Repository Skeleton --- composer.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/composer.json b/composer.json index 9aa3cd0b..c637e7ce 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ "authors": [ { "name": "Sebastian Bürgin-Fix", - "email": "sebastian.buergin@buergin.ch", + "email": "helpdesk@codebar.ch", "homepage": "https://www.codebar.ch", "role": "Software-Engineer" } @@ -25,9 +25,9 @@ "guzzlehttp/guzzle": "^7.8", "illuminate/contracts": "^12.0", "nesbot/carbon": "^3.8", - "saloonphp/cache-plugin": "^3.0", - "saloonphp/laravel-plugin": "^3.5", - "saloonphp/saloon": "^3.10.1", + "saloonphp/cache-plugin": "^3.1", + "saloonphp/laravel-plugin": "^4.0", + "saloonphp/saloon": "^4.0", "spatie/laravel-package-tools": "^1.19" }, "require-dev": { From a9fbe3128f04b19fd2597b22212a660496ea3fe2 Mon Sep 17 00:00:00 2001 From: Sebastian Fix Date: Thu, 26 Mar 2026 07:56:45 +0100 Subject: [PATCH 22/29] Optimized Repository Skeleton --- composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index c637e7ce..409fc869 100644 --- a/composer.json +++ b/composer.json @@ -26,8 +26,8 @@ "illuminate/contracts": "^12.0", "nesbot/carbon": "^3.8", "saloonphp/cache-plugin": "^3.1", - "saloonphp/laravel-plugin": "^4.0", - "saloonphp/saloon": "^4.0", + "saloonphp/laravel-plugin": "^3.0|^4.0", + "saloonphp/saloon": "^3.0|^4.0", "spatie/laravel-package-tools": "^1.19" }, "require-dev": { From 74c8e09df0de4057b611bcdbeeedddd7e01c2e7d Mon Sep 17 00:00:00 2001 From: Sebastian Fix Date: Thu, 26 Mar 2026 08:13:26 +0100 Subject: [PATCH 23/29] chore: sync dependency audit workflows from template --- .github/workflows/composer-audit.yml | 37 ++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 .github/workflows/composer-audit.yml diff --git a/.github/workflows/composer-audit.yml b/.github/workflows/composer-audit.yml new file mode 100644 index 00000000..ed5c5478 --- /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.4' + coverage: none + + - name: Resolve dependencies and audit + run: | + composer update --no-interaction --no-ansi + composer audit From 62b59c311faa94e608acf62a375812e544385d4a Mon Sep 17 00:00:00 2001 From: Sebastian Fix Date: Fri, 3 Apr 2026 15:11:35 +0200 Subject: [PATCH 24/29] chore: sync GitHub issue templates --- .github/ISSUE_TEMPLATE/bug_report.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 From 7209cf881777dd40fb40f6e23ea3d65a4c91a493 Mon Sep 17 00:00:00 2001 From: Sebastian Fix Date: Fri, 3 Apr 2026 15:11:38 +0200 Subject: [PATCH 25/29] chore: sync Laravel Pint workflow from template --- .github/workflows/fix-php-code-style-issues.yml | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) 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 From e7a27df4763953fad69dbe24b597e64bfb457f85 Mon Sep 17 00:00:00 2001 From: Sebastian Fix Date: Fri, 3 Apr 2026 15:11:44 +0200 Subject: [PATCH 26/29] chore: sync PHPStan workflow from template --- .github/workflows/phpstan.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 9a02f11bf3b1afc80b90d2c9a97f00a47d6b1f86 Mon Sep 17 00:00:00 2001 From: Sebastian Fix Date: Fri, 3 Apr 2026 15:11:45 +0200 Subject: [PATCH 27/29] chore: sync dependency audit workflows from template --- .github/workflows/composer-audit.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/composer-audit.yml b/.github/workflows/composer-audit.yml index ed5c5478..72698cfa 100644 --- a/.github/workflows/composer-audit.yml +++ b/.github/workflows/composer-audit.yml @@ -28,7 +28,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@2.37.0 with: - php-version: '8.4' + php-version: '8.5' coverage: none - name: Resolve dependencies and audit From d843cfce2aefa3941ddffabc6a801c83795bc866 Mon Sep 17 00:00:00 2001 From: Sebastian Fix Date: Sat, 4 Apr 2026 00:10:04 +0200 Subject: [PATCH 28/29] chore: sync Dependabot config and workflows from template --- .github/workflows/dependabot-auto-merge.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 }}" From fec181b645904093a24b06ff10c92ed03c26da05 Mon Sep 17 00:00:00 2001 From: Sebastian Fix Date: Tue, 7 Apr 2026 16:04:05 +0200 Subject: [PATCH 29/29] Laravel 13 --- .github/workflows/run-tests.yml | 14 ++-- composer.json | 41 +++++----- phpstan.neon.dist | 2 +- phpunit.xml.dist | 2 +- src/Connectors/DocuWareConnector.php | 45 +++++------ .../OAuth/IdentityServiceConfiguration.php | 65 ++++++++++++--- src/DTO/Documents/Document.php | 73 ++++++++++++++++- src/DTO/Documents/DocumentField.php | 6 ++ .../Documents/DocumentIndex/IndexDateDTO.php | 5 +- .../DocumentIndex/IndexDateTimeDTO.php | 5 +- .../DocumentIndex/IndexDecimalDTO.php | 3 + .../DocumentIndex/IndexKeywordDTO.php | 3 + .../Documents/DocumentIndex/IndexMemoDTO.php | 3 + .../DocumentIndex/IndexNumericDTO.php | 3 + .../Documents/DocumentIndex/IndexTableDTO.php | 33 +++++++- .../Documents/DocumentIndex/IndexTextDTO.php | 3 + .../Documents/DocumentIndex/PrepareDTO.php | 8 ++ src/DTO/Documents/DocumentPaginator.php | 25 +++++- src/DTO/Documents/TableRow.php | 22 ++++- src/DTO/Documents/TrashDocumentPaginator.php | 47 +++++++++-- src/DTO/General/Organization/Organization.php | 11 +++ src/DTO/SuggestionField.php | 9 +++ src/DocuWareSearchRequestBuilder.php | 25 +++++- src/Facades/DocuWare.php | 18 ++--- .../UpdateIndexValuesResponse.php | 6 +- src/Responses/Fields/GetFieldsResponse.php | 9 ++- .../Dialogs/GetAllDialogsResponse.php | 9 ++- ...llFileCabinetsAndDocumentTraysResponse.php | 9 ++- .../Organization/GetOrganizationResponse.php | 9 ++- .../GetModifyGroups/GetGroupsResponse.php | 9 ++- .../GetModifyRoles/GetRolesResponse.php | 9 ++- .../GetUsers/GetUsersResponse.php | 9 ++- .../GetDocumentWorkflowHistoryResponse.php | 6 +- src/Support/Auth.php | 16 +++- src/Support/JsonArrays.php | 25 ++++++ src/Support/ParseValue.php | 44 ++++++++-- tests/Pest.php | 80 +++++++++++++------ tests/TestCase.php | 27 ------- 38 files changed, 554 insertions(+), 184 deletions(-) create mode 100644 src/Support/JsonArrays.php 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 [