Replaces \$utopia->match(\$request) calls in the project, team, and auth
resolvers with the per-request RouteMatch injected directly via DI:
- app/init/resources/request.php — project / team resource closures now
declare 'match' as a dependency and read \$match?->route.
- app/controllers/shared/api/auth.php — auth init hook injects 'match'
instead of resolving via \$utopia.
- app/controllers/shared/api.php — drop the redundant re-match in the
storage cache init hook; \$route from the action's \$match inject is
already in scope.
Only Resolvers::resolve still calls \$utopia->match(...) — that path
genuinely matches a synthesized sub-request URL.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bumps utopia-php/http to the latest fix/concurrency-shared-state, which
collapses the separate route / matchedPath / arguments context keys into
a single immutable RouteMatch under the 'match' key.
- Replace ->inject('route') / ->inject('matchedPath') with
->inject('match'), reading \$match->route / \$match->path.
- Replace the manual array_merge(\$route->getPathValues(), \$request->
getParams()) workaround in api.php's shutdown hook with the framework-
provided \$match->arguments — same data the action saw, no path-value
reconstruction needed.
- Update GraphQL resolver to snapshot/restore the 'match' value instead
of 'route'.
- Update top-level call sites in app/http.php that read from getResource
('route') to read getResource('match')->route.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The api shutdown hook substitutes route labels like
\`cache.resource = 'file/{request.fileId}'\` against \$requestParams. We
were reading those from \$request->getParams(), which only returns body
or query params — path params (\`fileId\`, \`bucketId\`) were missing,
so cache documents got written with literal \`file/{request.fileId}\`
strings and subsequent cache hits 404'd because the placeholder was
treated as a real fileId.
Merge \$route->getPathValues(\$request) ahead of \$request->getParams() so
path params are available for substitution. Replicates the previous
behaviour of \$route->getParamsValues(), which was populated by the
upstream Hook param writeback that has since been removed.
Also rename the per-request container variable to \$context and the
loader callable to \$registerContext in app/http.php to match the new
upstream terminology.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Use ->inject('route') and ->inject('matchedPath') in actions instead of
reading via \$utopia->getResource() — this was the cause of the e2e 500s,
the bus resolver was hitting the global container after the upstream
Adapter::getContainer() semantics changed.
- Switch the bus resolver in app/http.php to use \$swooleAdapter->getContext()
so per-request resources (locale, platform, dbForProject) resolve from the
per-coroutine context container.
- Drop the dead ?->label('router', true) calls in general.php — the label was
never read.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adopts the breaking changes from utopia-php/http#251 (concurrency races on
shared Http/Route singletons):
- Replace `Http::getRoute()` / `setRoute()` with `getResource('route')` and
context container writes.
- Replace `Route::getMatchedPath()` with `getResource('matchedPath')`.
- Use `Adapter::getContext()` for the per-request container in `app/http.php`
(`getContainer()` now always returns the global singleton).
- Read request params from `$request->getParams()` in the api shutdown hook
instead of `Route::getParamsValues()`, which is no longer populated.
- Update Swoole promise context key to `__utopia_http_context`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Raises `phpstan.neon` level from 3 to 4 and fixes the 549 new errors
that level 4 surfaces across 157 files. Fixes are root-cause — no
`@phpstan-ignore`, no `@var` casts, no baseline entries, no widened
types. A handful of latent bugs were fixed along the way:
- `app/controllers/general.php`: path-traversal guard was negating
`\substr(...)` before the strict comparison (`!\substr(...) === $base`
was always `false === $base`). Rewritten as `\substr(...) !== $base`.
- `src/Appwrite/Platform/Modules/Databases/Http/Databases/Logs/XList.php`
and `.../TablesDB/Logs/XList.php`: were importing the raw Matomo
`DeviceDetector` (whose `getDevice()` returns `?int`) but treating the
result as an array with `deviceName/deviceBrand/deviceModel` keys.
Swapped to `Appwrite\Detector\Detector`, matching the wrapper already
used a few lines below for `$os`/`$client`.
- `src/Appwrite/Platform/Modules/Functions/Workers/Builds.php`: a match
key was checking `$resourceKey === 'functions'` when `$resourceKey`
is `'functionId'|'siteId'` — always false. Switched to the intended
`$resource->getCollection() === 'functions'` check.
- `src/Appwrite/OpenSSL/OpenSSL.php`: `encrypt()` return type tightened
to `string|false` to match `openssl_encrypt`; this lets callers'
`=== false` error handling remain meaningful.
- `app/controllers/api/messaging.php`: removed a dead
`array_key_exists('from', [])` branch in the Msg91 provider (empty
array literal; branch was unreachable).
Large cleanup categories across the 549 fixes:
- Removed redundant `?? default` on array offsets and expressions that
PHPStan now knows are non-nullable.
- Removed unreachable statements (mostly `return;` after `throw` or
`markTestSkipped()`).
- Removed redundant `is_array`/`is_string`/`is_bool`/`instanceof` checks
on already-narrowed types.
- Added `default =>` arms (or throwing arms) to non-exhaustive matches
on `string`/`mixed` input.
- Removed dead `$document === false` branches where method return types
were tightened to non-nullable `Document`.
- Removed unused properties (`$version` on Etsy/Zoom OAuth2, `$paths` on
Installer State, `$source` on MigrationsWorker, `$account2` on two
GraphQL auth tests), unused traits (`ApiVectorsDB`, `DatabaseFixture`),
and an unused `cleanupStaleExecutions` task method.
- Replaced `assertTrue(true)` and redundant `assertIsArray`/`assertIsString`/
`assertNotNull` assertions with `addToAssertionCount(1)` or
`assertNotEmpty` where the runtime type was already known.
Cache write hook now checks HTTP status code before writing to prevent
failed AVIF (or any other) conversions from poisoning the cache.
Bumps utopia-php/image to 0.8.5 which fixes AVIF/HEIC output by using
native Imagick instead of the deprecated magick convert shell command.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Three bugs causing storage preview cache to be ineffective:
1. Cache keys included the `token` auth parameter, so requests using
resource tokens always generated unique keys and never hit cache.
Introduced `cache.params` label for routes to opt-in specific params
into the cache key; preview now declares only the transform params.
2. Cache hits never refreshed `accessedAt` in the DB or the filesystem
file mtime, because `$response->send()` in the init hook skips the
shutdown hook. After 30 days the maintenance job evicted still-active
cache entries, and after the original 30-day filesystem TTL the cache
file expired — causing periodic full re-renders. The cache-hit path
now updates both on the APP_CACHE_UPDATE (24h) interval.
3. `updateDocument` in the preview action passed the full file document
instead of a sparse one when updating `transformedAt`.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- general.php: add instanceof guard in error handler to prevent calling
isPrivileged() on a plain Document if getResource('user') returns
an unexpected type
- graphql.php: add setUser() calls on request/response in graphql group
init so sensitive field filtering works correctly for GraphQL routes
- api.php: fix session group init type hint from Document to User for
consistency with all other init blocks
https://claude.ai/code/session_01JLPDurUgyj7qViA8JqQFTH
All call sites now use $user->isApp() and $user->isPrivileged() instance
syntax instead of static User::isApp() / $user::isPrivileged() calls.
Added setUser() to Request class for consistency with Response.
https://claude.ai/code/session_01JLPDurUgyj7qViA8JqQFTH
PHPStan correctly flagged that Document::isPrivileged() doesn't exist.
Changed type hints from Document $user to User $user in all action
signatures where $user::isPrivileged() is called, since the runtime
instance is always a User (or subclass).
https://claude.ai/code/session_01JLPDurUgyj7qViA8JqQFTH
Replace all static User::isPrivileged() calls with $user::isPrivileged()
across the codebase. Since $user is resolved via setDocumentType, this
allows subclasses to override the privilege check without CE needing to
know about downstream-specific roles.
https://claude.ai/code/session_01JLPDurUgyj7qViA8JqQFTH
Introduce granular audit user types to differentiate between regular
users, console admins, guests, and the various API key scopes.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Introduced a new API endpoint to update user impersonator capability.
- Enhanced user model to include impersonator attributes.
- Updated database schema to support impersonation.
- Implemented impersonation logic in the request handling to allow users with impersonator capability to act as other users.
- Added relevant API documentation for impersonation headers.
This feature allows users with the appropriate permissions to impersonate other users, enhancing flexibility in user management.
- Remove var_dump debug calls leaking API keys to stdout
- Stop embedding secret keys in HTML data attributes on upgrades
- Strip sensitive fields from sessionStorage install lock
- Quote hostPath in Docker Compose YAML template
- Remove stack traces from client-facing error responses
- Strip sessionSecret and traces from Status endpoint response
- Fix undefined $input variable (should be $userInput) in CLI install
- Add backtick escaping in .env template to prevent shell injection
- Add 2-hour timeout to isInstallationComplete infinite loop
- Escape user-supplied startCommand in shell strings
- Add LOCK_EX to progress file writes
- Fix typo in Upgrade.php error message
- Remove unused variable in V21 response filter
- Remove dead code in applyLockPayload after sessionStorage sanitization
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>