Skip to content

fix: Fix Psalm errors and configure static analysis#188

Open
rubenvdlinde wants to merge 55 commits intomainfrom
development
Open

fix: Fix Psalm errors and configure static analysis#188
rubenvdlinde wants to merge 55 commits intomainfrom
development

Conversation

@rubenvdlinde
Copy link
Contributor

Summary

  • Fix 4 InvalidNamedArgument bugs where method call sites used wrong parameter names
  • Fix UndefinedThisPropertyFetch (non-existent $this->_userManager property)
  • Fix named argument mismatches in Application.php DI registrations, SoftwareCatalogEventListener, and ContactpersoonService
  • Configure psalm.xml with suppressions for all OCA\OpenRegister\* runtime classes and missing OCP\* stubs (ICache, IGroup, ISession, IWidget, IRepairStep, DoesNotExistException, IAccountManager)
  • Remove phpro/grumphp from require-dev (conflicts with vimeo/psalm via amphp/amp); update composer.lock

Test plan

  • PHP Quality CI passes including new Psalm step
  • PHPUnit unit tests pass
  • PHPCS and PHPMD pass

🤖 Generated with Claude Code

remko48 and others added 30 commits February 19, 2026 11:54
- Add koppelingType field that auto-computes to "extern" when
  buitengemeentelijkVoorziening is filled, "intern" otherwise
- Update naam default template with moduleB|buitengemeentelijkVoorziening
  fallback so external koppelingen show the BGV name
- Update objectNameField in configuration to match

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ianceService

The UUID was being looked up from the object data array ($moduleData['uuid'])
which doesn't contain the UUID field. Changed to use $moduleObject->getUuid()
which correctly reads it from the entity metadata.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…rname backfill

The listener searched contactpersonen by username field only, which was
null for existing/imported data. Added case-insensitive email fallback
using _search (ILIKE), username backfill on first match, and direct
mapper save to bypass schema validation of legacy data.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move background job registration from boot() (runs every request) to
info.xml (runs once at install). Fixes wrong class reference in info.xml.

- Remove $jobList->has() DB query from boot()
- Fix info.xml: Cron\OrganizationSync → BackgroundJob\OrganizationContactSyncJob

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ter)

After creating a user account, fork a background process that replicates
Nextcloud's first-login filesystem setup (setupFS, copySkeleton,
updateLastLoginTimestamp). This shifts the ~4s cost from the user's first
login to an async fire-and-forget process, so both the admin response and
the user's first login are fast.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- performOrganizationsSync: query per-schema magic table instead of
  empty openregister_objects blob table, with LEFT JOIN to find only
  orgs missing entities. Batch-processes with email suppression.
- performContactSync: query magic table with INNER JOIN on accounts_data
- performUserSync: query magic table with org entity join
- Add backup entity creation in createOrUpdateContactPersonObject and
  processSpecificContactPerson when org entity missing
- Wrap saveObject in try/catch so event listener failures don't block
  user creation tracking (addUserToOrganizationEntity, stats)
- Fix ObjectEntity array access in SettingsService syncOrganisations
- Clean up debug log levels (remove emoji critical logs)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- ContactpersoonService: add backup org entity creation when entity
  is missing during processContactpersoon() (same pattern as
  OrganizationSyncService backup). Catches DoesNotExistException and
  creates entity from org object if org is active.
- OrganizationSyncService: add ensureOrganisationEntityPublic() wrapper
  for cross-service access to the private ensureOrganisationEntity()
- OrganisatieService: cast naam/type to string to prevent TypeError
  when org name is numeric (e.g. "2150" stored as int)
- SettingsService: handle ObjectEntity in search results (getUuid()
  instead of array access)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
saveObject() to update contact username triggers ObjectUpdatedEvent,
which re-enters processContactpersoon() via handleContactpersoonUpdate,
causing an infinite loop. Add static recursion guard that tracks
contact UUIDs currently being processed — re-entry returns immediately.
Cleared in finally block to ensure guard is always released.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add UUID and slug pre-checks in ensureOrganizationEntity() before
attempting to create a new organisation entity. This prevents the
SQLSTATE[23505] unique constraint violations on the slug column that
were crashing production.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Multiple bugs caused contact persons to lose their organisatie field
(the link back to their organisation) when an organisation was activated:

1. The organisatie field was permanently destroyed by unset() calls
   before saveObject(). Now saved before unset and restored after save
   at all 4 locations.

2. processRelatedContactPersons could not find contacts linked via
   inversedBy (org→contact) because it only searched by the contact's
   organisatie field (which was null). Added fallback that looks up
   contacts from the org's contactpersonen UUID array.

3. Related contacts now get their organisatie field set before being
   passed to processSpecificContactPerson, ensuring user creation
   proceeds correctly.

4. updateContactpersoonObjectOwner now restores a missing organisatie
   field as a safety net.

5. Downgraded debug-only critical log levels to info.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Leverancier organisations registered by gemeenten are no longer publicly
visible. Public users now only see organisations that are: registered by
a Leverancier, of type Gemeente, or of type Samenwerking. Aanbod-beheerders
only see their own organisation. Gebruik-beheerders retain full access.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The dienst schema had two properties with title "Diensttype": `type`
and `dienstType`, both with table.default=true, causing a duplicate
column in the diensten table. Removed `dienstType` and made `type`
the facetable field with aggregated=false to prevent cross-schema
facet mixing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replaces empty placeholder content blocks (ph1, ph2) with actual
seed data for the quote section and 3 content blocks on the front page.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
rubenvdlinde and others added 25 commits February 24, 2026 09:58
ModuleVersionService was never registered as a service, causing
ModuleComplianceSubscriber to silently fail when trying to create
default 1.0.0 versions for new modules. The service was properly
implemented but the container registration was missing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace existing license (Apache-2.0/AGPL) with EUPL-1.2 across all
metadata files: LICENSE, appinfo/info.xml, composer.json, package.json.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…5, #373, #377, #394)

Remove empty rollen enum array and add Gebruik-raadpleger to items enum.
Add table.default=true to diensten and standaardVersies schemas for
beheer table visibility. Add authorization.read=['authenticated'] to
contactpersonen, e-mailadres, and telefoonnummer properties.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… #434, #352)

Replace saveObject with ObjectEntityMapper.update() in registration
flow to avoid validation cascades. Add email subaddress sanitization
for username generation. Make ObjectModal support 12 object types via
generic config. Add metadata hydration after user profile sync.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…schema

moduleB now only references modules (removed oneOf with element).
buitengemeentelijkVoorziening is now visible and shown in default table view.
The UI keeps a single merged dropdown that maps to the correct field based on type.

Resolves ambiguity from #433 where moduleB accepted both modules and elements.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
v0.1.139 already exists as a stable release in the Nextcloud appstore,
causing the nightly upload to fail with HTTP 400.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The Nextcloud appstore only accepts a fixed set of licence identifiers
(agpl, AGPL-3.0-or-later, MIT, Apache-2.0, etc). EUPL-1.2 is not in
the accepted list, causing all appstore uploads to fail with HTTP 400
since Feb 26.

The actual LICENSE file remains EUPL-1.2 — this only changes the
appstore metadata identifier. EUPL 1.2 has an explicit compatibility
clause with AGPL-3.0.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
OrganizationSyncService: add null-coalescing fallbacks for missing
usersCreated/usersUpdated keys in sync result arrays.
OrganisatieService: reduce catch block logging to only error message
and UUID instead of full object dump with stack trace.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
All sourceUrl and configuration references in the register JSON were
using github.com/blob/ URLs which return HTML pages, not raw JSON.
Converted to raw.githubusercontent.com for proper JSON fetching by
the ConfigurationCheckJob cronjob.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The sync cronjob no longer needs user/org context since all ObjectService
calls now explicitly pass _rbac: false and _multitenancy: false. This is
correct because sync is a system-level operation.

Changes:
- OrganizationContactSyncJob: removed CronjobContextTrait usage and
  context warnings, simplified to direct service call
- OrganizationSyncService: added _rbac: false, _multitenancy: false to
  4 ObjectService calls that were using defaults
- CronjobContextTrait: marked as @deprecated
- SettingsService: marked 6 cronjob config methods as @deprecated
- SettingsController: marked 4 cronjob config endpoints as @deprecated

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add vimeo/psalm ^5.26, phpstan/phpstan ^1.10
- Add nextcloud/coding-standard, phpcsstandards/phpcsextra, nextcloud/ocp
- Create phpstan.neon config
- Add phpcs-custom-sniffs/ (named parameters enforcement)
- Rewrite phpcs.xml to target lib/ + add custom sniff reference
- Add phpmetrics:violations, phpcs:output, phpstan scripts
- Fix psalm script to use ./vendor/bin/psalm
- Add code-quality.yml GitHub Actions workflow (blocks PRs on phpcs+phpmd)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…rvice backup

Refactor all function calls to use named arguments across controllers,
services, event listeners, background jobs, and settings to comply with
the Conduction PHPCS coding standard. Removes the leftover
ArchiMateService_backup.php file.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The NC organisation UUID from objectData['organisation'] (RBAC system field)
is different from the register organisatie object UUID. When an NC org hasn't
been synced to a register object yet, the lookup fails with "Object not found".

This is expected behaviour, not an error — the caller already handles the
empty string return value gracefully. Downgrade from error to warning and
simplify the log context.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fixed PHPCS violations in 32 lib/ files (all at 0 errors). Changes include:
- Added declare(strict_types=1) to all files missing it
- Fixed property and method doc blocks (short descriptions, @param/@return order)
- Replaced operator ! with === false/true explicit comparisons
- Replaced implicit true comparisons with === true
- Added named parameters to function calls
- Expanded inline ternary IFs to if/else blocks
- Fixed inline comments to end with periods
- Added end-of-block comments (//end method())
- Fixed file doc comment formatting

Large service classes intentionally skipped (too many violations for this pass):
ArchiMateService (517), ArchiMateExportService (183), ArchiMateImportService (1174),
SettingsService (527), SoftwareCatalogueService (319), OrganizationSyncService (291),
ContactPersonHandler (252).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a complete Docusaurus 3.7 setup matching the pipelinq pattern:
- docusaurus/ with config, custom.css (OWC green theme), homepage
- docs/FEATURES.md with feature overview
- .github/workflows/documentation.yml deploying to softwarecatalog.app on push to development
- Blue hexagon app-store.svg as navbar logo

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Named parameter 'appName:' breaks on Nextcloud versions where the concrete
URLGenerator still uses '$app' as the parameter name.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add tests/bootstrap-unit.php with OCP class autoloader (no Nextcloud bootstrap)
- Update .github/workflows/code-quality.yml to run PHPUnit unit tests on every PR
  (uses --bootstrap tests/bootstrap-unit.php to skip Integration test setup)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add Psalm (Static Analysis) step to code-quality.yml php-quality job

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fix 4 InvalidNamedArgument bugs (wrong param names in method calls):
  - AangebodenGebruikService: query → baseQuery in addQueryFilters()
  - OrganisatieService: userData → user, organisationData → organization
  - HierarchyHandler: manager → managerUsername in setUserManager()
  - OrganisatieService: createOrganisation → name in createOrganisation()
- Fix UndefinedThisPropertyFetch: replace non-existent $this->_userManager
  with $this->_container->get(IUserManager::class) in SoftwareCatalogueService
- Fix named args in Application.php DI container registrations
- Fix named args in SoftwareCatalogEventListener and ContactpersoonService
- Update psalm.xml: add suppressions for all OCA\OpenRegister\* and missing
  OCP\* runtime classes (ICache, IGroup, ISession, IWidget, IRepairStep, etc.)
- Remove phpro/grumphp from require-dev (conflicts with vimeo/psalm via
  amphp/amp dependency chain); update composer.lock accordingly

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Removed InvalidArgument, InvalidReturnType, InvalidReturnStatement,
InvalidCast, InvalidMethodCall suppressors from psalm.xml — exposing 5
real bugs that were previously hidden:

- ArchiMateExportService::createSectionFolder(): return type was
  SimpleXMLElement but function can return null; fixed to ?SimpleXMLElement
- ArchiMateExportService::extractModelMetadata(): return can be
  ArrayAccess; cast to (array) to satisfy array return type
- ContactPersonHandler::assignUserGroups(): docblock said @return void
  but function signature and body return string; fixed docblock

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Tested all remaining suppressors — removed those with 0 false positives:
- ImplementedReturnTypeMismatch (0 errors without it)
- NullableReturnStatement (0 errors without it)

Confirmed as legitimate suppressors (tested, all false positives):
- TypeDoesNotContainNull/Type, RedundantCondition: constant feature-flag patterns
- InvalidArrayOffset/Access, EmptyArrayAccess: Psalm inference cascade from OCA\OpenRegister unknowns
- UnusedVariable, NoValue: SimpleXMLElement::addChild() side-effect patterns

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants