chore: bump PHPStan to level 4 and fix all new errors

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.
This commit is contained in:
Chirag Aggarwal
2026-04-19 17:31:20 +05:30
parent 0aab3e9a43
commit d2230f8fe7
157 changed files with 378 additions and 1129 deletions
+26 -29
View File
@@ -133,9 +133,6 @@ $createSession = function (string $userId, string $secret, Request $request, Res
});
$provider = match ($verifiedToken->getAttribute('type')) {
TOKEN_TYPE_VERIFICATION,
TOKEN_TYPE_RECOVERY,
TOKEN_TYPE_INVITE => SESSION_PROVIDER_EMAIL,
TOKEN_TYPE_MAGIC_URL => SESSION_PROVIDER_MAGIC_URL,
TOKEN_TYPE_PHONE => SESSION_PROVIDER_PHONE,
TOKEN_TYPE_OAUTH2 => $oauthProvider,
@@ -335,15 +332,15 @@ Http::post('/v1/account')
throw new Exception(Exception::GENERAL_INVALID_EMAIL);
}
if (($plan['supportsDisposableEmailValidation'] ?? false) && ($project->getAttribute('auths', [])['disposableEmails'] ?? false) && ($emailMetadata['emailIsDisposable'] ?? false)) {
if (($plan['supportsDisposableEmailValidation'] ?? false) && ($project->getAttribute('auths', [])['disposableEmails'] ?? false) && $emailMetadata['emailIsDisposable']) {
throw new Exception(Exception::USER_EMAIL_DISPOSABLE);
}
if (($plan['supportsCanonicalEmailValidation'] ?? false) && ($project->getAttribute('auths', [])['canonicalEmails'] ?? false) && ($emailMetadata['emailIsCanonical'] ?? true) === false) {
if (($plan['supportsCanonicalEmailValidation'] ?? false) && ($project->getAttribute('auths', [])['canonicalEmails'] ?? false) && $emailMetadata['emailIsCanonical'] === false) {
throw new Exception(Exception::USER_EMAIL_NOT_CANONICAL);
}
if (($plan['supportsFreeEmailValidation'] ?? false) && ($project->getAttribute('auths', [])['freeEmails'] ?? false) && ($emailMetadata['emailIsFree'] ?? false)) {
if (($plan['supportsFreeEmailValidation'] ?? false) && ($project->getAttribute('auths', [])['freeEmails'] ?? false) && $emailMetadata['emailIsFree']) {
throw new Exception(Exception::USER_EMAIL_FREE);
}
@@ -837,7 +834,7 @@ Http::patch('/v1/account/sessions/:sessionId')
throw new Exception(Exception::PROJECT_PROVIDER_UNSUPPORTED);
}
if (!empty($provider) && $className !== null && \class_exists($className)) {
if (!empty($provider) && \class_exists($className)) {
$appId = $project->getAttribute('oAuthProviders', [])[$provider . 'Appid'] ?? '';
$appSecret = $project->getAttribute('oAuthProviders', [])[$provider . 'Secret'] ?? '{}';
@@ -1604,7 +1601,7 @@ Http::get('/v1/account/sessions/oauth2/:provider/redirect')
}
}
if ($user === false || $user->isEmpty()) { // No user logged in or with OAuth2 provider ID, create new one or connect with account with same email
if ($user->isEmpty()) { // No user logged in or with OAuth2 provider ID, create new one or connect with account with same email
if (empty($email)) {
$failureRedirect(Exception::USER_UNAUTHORIZED, 'OAuth provider failed to return email.');
}
@@ -1621,7 +1618,7 @@ Http::get('/v1/account/sessions/oauth2/:provider/redirect')
}
// If user is not found, check if there is a user with the same email
if ($user === false || $user->isEmpty()) {
if ($user->isEmpty()) {
$userWithEmail = $dbForProject->findOne('users', [
Query::equal('email', [$email]),
]);
@@ -1634,7 +1631,7 @@ Http::get('/v1/account/sessions/oauth2/:provider/redirect')
}
// If user is not found, check if there is an identity with the same email
if ($user === false || $user->isEmpty()) {
if ($user->isEmpty()) {
$identityWithMatchingEmail = $dbForProject->findOne('identities', [
Query::equal('providerEmail', [$email]),
]);
@@ -1646,7 +1643,7 @@ Http::get('/v1/account/sessions/oauth2/:provider/redirect')
}
}
if ($user === false || $user->isEmpty()) { // Last option -> create the user
if ($user->isEmpty()) { // Last option -> create the user
$limit = $project->getAttribute('auths', [])['limit'] ?? 0;
if ($limit !== 0) {
@@ -1679,15 +1676,15 @@ Http::get('/v1/account/sessions/oauth2/:provider/redirect')
$failureRedirect(Exception::GENERAL_INVALID_EMAIL);
}
if (($plan['supportsDisposableEmailValidation'] ?? false) && ($project->getAttribute('auths', [])['disposableEmails'] ?? false) && ($emailMetadata['emailIsDisposable'] ?? false)) {
if (($plan['supportsDisposableEmailValidation'] ?? false) && ($project->getAttribute('auths', [])['disposableEmails'] ?? false) && $emailMetadata['emailIsDisposable']) {
$failureRedirect(Exception::USER_EMAIL_DISPOSABLE);
}
if (($plan['supportsCanonicalEmailValidation'] ?? false) && ($project->getAttribute('auths', [])['canonicalEmails'] ?? false) && ($emailMetadata['emailIsCanonical'] ?? true) === false) {
if (($plan['supportsCanonicalEmailValidation'] ?? false) && ($project->getAttribute('auths', [])['canonicalEmails'] ?? false) && $emailMetadata['emailIsCanonical'] === false) {
$failureRedirect(Exception::USER_EMAIL_NOT_CANONICAL);
}
if (($plan['supportsFreeEmailValidation'] ?? false) && ($project->getAttribute('auths', [])['freeEmails'] ?? false) && ($emailMetadata['emailIsFree'] ?? false)) {
if (($plan['supportsFreeEmailValidation'] ?? false) && ($project->getAttribute('auths', [])['freeEmails'] ?? false) && $emailMetadata['emailIsFree']) {
$failureRedirect(Exception::USER_EMAIL_FREE);
}
@@ -1820,15 +1817,15 @@ Http::get('/v1/account/sessions/oauth2/:provider/redirect')
$failureRedirect(Exception::GENERAL_INVALID_EMAIL);
}
if (($plan['supportsDisposableEmailValidation'] ?? false) && ($project->getAttribute('auths', [])['disposableEmails'] ?? false) && ($emailMetadata['emailIsDisposable'] ?? false)) {
if (($plan['supportsDisposableEmailValidation'] ?? false) && ($project->getAttribute('auths', [])['disposableEmails'] ?? false) && $emailMetadata['emailIsDisposable']) {
$failureRedirect(Exception::USER_EMAIL_DISPOSABLE);
}
if (($plan['supportsCanonicalEmailValidation'] ?? false) && ($project->getAttribute('auths', [])['canonicalEmails'] ?? false) && ($emailMetadata['emailIsCanonical'] ?? true) === false) {
if (($plan['supportsCanonicalEmailValidation'] ?? false) && ($project->getAttribute('auths', [])['canonicalEmails'] ?? false) && $emailMetadata['emailIsCanonical'] === false) {
$failureRedirect(Exception::USER_EMAIL_NOT_CANONICAL);
}
if (($plan['supportsFreeEmailValidation'] ?? false) && ($project->getAttribute('auths', [])['freeEmails'] ?? false) && ($emailMetadata['emailIsFree'] ?? false)) {
if (($plan['supportsFreeEmailValidation'] ?? false) && ($project->getAttribute('auths', [])['freeEmails'] ?? false) && $emailMetadata['emailIsFree']) {
$failureRedirect(Exception::USER_EMAIL_FREE);
}
@@ -1954,7 +1951,7 @@ Http::get('/v1/account/sessions/oauth2/:provider/redirect')
->addCookie($store->getKey(), $encoded, (new \DateTime($expire))->getTimestamp(), '/', $cookieDomain, ('https' == $protocol), true, Config::getParam('cookieSamesite'));
}
if (isset($sessionUpgrade) && $sessionUpgrade && isset($session)) {
if (isset($sessionUpgrade) && isset($session)) {
foreach ($user->getAttribute('targets', []) as $target) {
if ($target->getAttribute('providerType') !== MESSAGE_TYPE_PUSH) {
continue;
@@ -2178,15 +2175,15 @@ Http::post('/v1/account/tokens/magic-url')
throw new Exception(Exception::GENERAL_INVALID_EMAIL);
}
if (($plan['supportsDisposableEmailValidation'] ?? false) && ($project->getAttribute('auths', [])['disposableEmails'] ?? false) && ($emailMetadata['emailIsDisposable'] ?? false)) {
if (($plan['supportsDisposableEmailValidation'] ?? false) && ($project->getAttribute('auths', [])['disposableEmails'] ?? false) && $emailMetadata['emailIsDisposable']) {
throw new Exception(Exception::USER_EMAIL_DISPOSABLE);
}
if (($plan['supportsCanonicalEmailValidation'] ?? false) && ($project->getAttribute('auths', [])['canonicalEmails'] ?? false) && ($emailMetadata['emailIsCanonical'] ?? true) === false) {
if (($plan['supportsCanonicalEmailValidation'] ?? false) && ($project->getAttribute('auths', [])['canonicalEmails'] ?? false) && $emailMetadata['emailIsCanonical'] === false) {
throw new Exception(Exception::USER_EMAIL_NOT_CANONICAL);
}
if (($plan['supportsFreeEmailValidation'] ?? false) && ($project->getAttribute('auths', [])['freeEmails'] ?? false) && ($emailMetadata['emailIsFree'] ?? false)) {
if (($plan['supportsFreeEmailValidation'] ?? false) && ($project->getAttribute('auths', [])['freeEmails'] ?? false) && $emailMetadata['emailIsFree']) {
throw new Exception(Exception::USER_EMAIL_FREE);
}
@@ -2488,15 +2485,15 @@ Http::post('/v1/account/tokens/email')
throw new Exception(Exception::GENERAL_INVALID_EMAIL);
}
if (($plan['supportsDisposableEmailValidation'] ?? false) && ($project->getAttribute('auths', [])['disposableEmails'] ?? false) && ($emailMetadata['emailIsDisposable'] ?? false)) {
if (($plan['supportsDisposableEmailValidation'] ?? false) && ($project->getAttribute('auths', [])['disposableEmails'] ?? false) && $emailMetadata['emailIsDisposable']) {
throw new Exception(Exception::USER_EMAIL_DISPOSABLE);
}
if (($plan['supportsCanonicalEmailValidation'] ?? false) && ($project->getAttribute('auths', [])['canonicalEmails'] ?? false) && ($emailMetadata['emailIsCanonical'] ?? true) === false) {
if (($plan['supportsCanonicalEmailValidation'] ?? false) && ($project->getAttribute('auths', [])['canonicalEmails'] ?? false) && $emailMetadata['emailIsCanonical'] === false) {
throw new Exception(Exception::USER_EMAIL_NOT_CANONICAL);
}
if (($plan['supportsFreeEmailValidation'] ?? false) && ($project->getAttribute('auths', [])['freeEmails'] ?? false) && ($emailMetadata['emailIsFree'] ?? false)) {
if (($plan['supportsFreeEmailValidation'] ?? false) && ($project->getAttribute('auths', [])['freeEmails'] ?? false) && $emailMetadata['emailIsFree']) {
throw new Exception(Exception::USER_EMAIL_FREE);
}
@@ -3397,15 +3394,15 @@ Http::patch('/v1/account/email')
throw new Exception(Exception::GENERAL_INVALID_EMAIL);
}
if (($plan['supportsDisposableEmailValidation'] ?? false) && ($project->getAttribute('auths', [])['disposableEmails'] ?? false) && ($emailMetadata['emailIsDisposable'] ?? false)) {
if (($plan['supportsDisposableEmailValidation'] ?? false) && ($project->getAttribute('auths', [])['disposableEmails'] ?? false) && $emailMetadata['emailIsDisposable']) {
throw new Exception(Exception::USER_EMAIL_DISPOSABLE);
}
if (($plan['supportsCanonicalEmailValidation'] ?? false) && ($project->getAttribute('auths', [])['canonicalEmails'] ?? false) && ($emailMetadata['emailIsCanonical'] ?? true) === false) {
if (($plan['supportsCanonicalEmailValidation'] ?? false) && ($project->getAttribute('auths', [])['canonicalEmails'] ?? false) && $emailMetadata['emailIsCanonical'] === false) {
throw new Exception(Exception::USER_EMAIL_NOT_CANONICAL);
}
if (($plan['supportsFreeEmailValidation'] ?? false) && ($project->getAttribute('auths', [])['freeEmails'] ?? false) && ($emailMetadata['emailIsFree'] ?? false)) {
if (($plan['supportsFreeEmailValidation'] ?? false) && ($project->getAttribute('auths', [])['freeEmails'] ?? false) && $emailMetadata['emailIsFree']) {
throw new Exception(Exception::USER_EMAIL_FREE);
}
@@ -3442,7 +3439,7 @@ Http::patch('/v1/account/email')
*/
$oldTarget = $user->find('identifier', $oldEmail, 'targets');
if ($oldTarget instanceof Document && !$oldTarget->isEmpty()) {
if (!$oldTarget->isEmpty()) {
$authorization->skip(fn () => $dbForProject->updateDocument('targets', $oldTarget->getId(), $oldTarget->setAttribute('identifier', $email)));
}
$dbForProject->purgeCachedDocument('users', $user->getId());
@@ -3531,7 +3528,7 @@ Http::patch('/v1/account/phone')
*/
$oldTarget = $user->find('identifier', $oldPhone, 'targets');
if ($oldTarget instanceof Document && !$oldTarget->isEmpty()) {
if (!$oldTarget->isEmpty()) {
$authorization->skip(fn () => $dbForProject->updateDocument('targets', $oldTarget->getId(), $oldTarget->setAttribute('identifier', $phone)));
}
$dbForProject->purgeCachedDocument('users', $user->getId());
+1 -14
View File
@@ -482,7 +482,6 @@ Http::post('/v1/messaging/providers/msg91')
$enabled === true
&& \array_key_exists('senderId', $credentials)
&& \array_key_exists('authKey', $credentials)
&& \array_key_exists('from', $options)
) {
$enabled = true;
} else {
@@ -3207,10 +3206,6 @@ Http::post('/v1/messaging/messages/email')
throw new Exception(Exception::MESSAGE_MISSING_TARGET);
}
if ($status === MessageStatus::SCHEDULED && \is_null($scheduledAt)) {
throw new Exception(Exception::MESSAGE_MISSING_SCHEDULE);
}
$mergedTargets = \array_merge($targets, $cc, $bcc);
if (!empty($mergedTargets)) {
@@ -3386,10 +3381,6 @@ Http::post('/v1/messaging/messages/sms')
throw new Exception(Exception::MESSAGE_MISSING_TARGET);
}
if ($status === MessageStatus::SCHEDULED && \is_null($scheduledAt)) {
throw new Exception(Exception::MESSAGE_MISSING_SCHEDULE);
}
if (!empty($targets)) {
$foundTargets = $dbForProject->find('targets', [
Query::equal('$id', $targets),
@@ -3527,10 +3518,6 @@ Http::post('/v1/messaging/messages/push')
throw new Exception(Exception::MESSAGE_MISSING_TARGET);
}
if ($status === MessageStatus::SCHEDULED && \is_null($scheduledAt)) {
throw new Exception(Exception::MESSAGE_MISSING_SCHEDULE);
}
if (!empty($targets)) {
$foundTargets = $dbForProject->find('targets', [
Query::equal('$id', $targets),
@@ -4660,7 +4647,7 @@ Http::delete('/v1/messaging/messages/:messageId')
if (!empty($scheduleId)) {
try {
$dbForPlatform->deleteDocument('schedules', $scheduleId);
} catch (Exception) {
} catch (\Throwable) {
// Ignore
}
}
+2 -1
View File
@@ -51,7 +51,8 @@ function getDatabaseTransferResourceServices(string $databaseType)
DATABASE_TYPE_LEGACY,
DATABASE_TYPE_TABLESDB => Transfer::GROUP_DATABASES_TABLES_DB,
DATABASE_TYPE_VECTORSDB => Transfer::GROUP_DATABASES_VECTOR_DB,
DATABASE_TYPE_DOCUMENTSDB => Transfer::GROUP_DATABASES_DOCUMENTS_DB
DATABASE_TYPE_DOCUMENTSDB => Transfer::GROUP_DATABASES_DOCUMENTS_DB,
default => throw new \LogicException('Unknown database type: ' . $databaseType),
};
}
+2 -1
View File
@@ -113,11 +113,12 @@ Http::get('/v1/project/usage')
$factor = match ($period) {
'1h' => 3600,
'1d' => 86400,
default => throw new \LogicException('Unsupported period: ' . $period),
};
$limit = match ($period) {
'1h' => (new DateTime($startDate))->diff(new DateTime($endDate))->days * 24,
'1d' => (new DateTime($startDate))->diff(new DateTime($endDate))->days
'1d' => (new DateTime($startDate))->diff(new DateTime($endDate))->days,
};
$format = match ($period) {
-1
View File
@@ -321,7 +321,6 @@ Http::patch('/v1/projects/:projectId/auth/:method')
$project = $dbForPlatform->getDocument('projects', $projectId);
$auth = Config::getParam('auth')[$method] ?? [];
$authKey = $auth['key'] ?? '';
$status = ($status === '1' || $status === 'true' || $status === 1 || $status === true);
if ($project->isEmpty()) {
throw new Exception(Exception::PROJECT_NOT_FOUND);
+5 -4
View File
@@ -1535,7 +1535,7 @@ Http::patch('/v1/users/:userId/email')
Query::equal('identifier', [$email]),
]);
if ($target instanceof Document && !$target->isEmpty()) {
if (!$target->isEmpty()) {
throw new Exception(Exception::USER_TARGET_ALREADY_EXISTS);
}
}
@@ -1600,7 +1600,7 @@ Http::patch('/v1/users/:userId/email')
*/
$oldTarget = $user->find('identifier', $oldEmail, 'targets');
if ($oldTarget instanceof Document && !$oldTarget->isEmpty()) {
if (!$oldTarget->isEmpty()) {
if (\strlen($email) !== 0) {
$dbForProject->updateDocument('targets', $oldTarget->getId(), new Document(['identifier' => $email]));
$oldTarget->setAttribute('identifier', $email);
@@ -1681,7 +1681,7 @@ Http::patch('/v1/users/:userId/phone')
Query::equal('identifier', [$number]),
]);
if ($target instanceof Document && !$target->isEmpty()) {
if (!$target->isEmpty()) {
throw new Exception(Exception::USER_TARGET_ALREADY_EXISTS);
}
}
@@ -1696,7 +1696,7 @@ Http::patch('/v1/users/:userId/phone')
*/
$oldTarget = $user->find('identifier', $oldPhone, 'targets');
if ($oldTarget instanceof Document && !$oldTarget->isEmpty()) {
if (!$oldTarget->isEmpty()) {
if ($number !== '') {
$dbForProject->updateDocument('targets', $oldTarget->getId(), new Document(['identifier' => $number]));
$oldTarget->setAttribute('identifier', $number);
@@ -2842,6 +2842,7 @@ Http::get('/v1/users/usage')
$format = match ($days['period']) {
'1h' => 'Y-m-d\TH:00:00.000P',
'1d' => 'Y-m-d\T00:00:00.000P',
default => throw new \LogicException('Unsupported period: ' . $days['period']),
};
foreach ($metrics as $metric) {
+30 -47
View File
@@ -67,7 +67,7 @@ Config::setParam('cookieSamesite', Response::COOKIE_SAMESITE_NONE);
function router(Http $utopia, Database $dbForPlatform, callable $getProjectDB, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Event $queueForEvents, Bus $bus, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, Authorization $authorization, ?Key $apiKey, DeleteEvent $queueForDeletes, int $executionsRetentionCount)
{
$host = $request->getHostname() ?? '';
$host = $request->getHostname();
if (!empty($previewHostname)) {
$host = $previewHostname;
}
@@ -200,12 +200,6 @@ function router(Http $utopia, Database $dbForPlatform, callable $getProjectDB, S
$deployment = $authorization->skip(fn () => $dbForProject->getDocument('deployments', $activeDeploymentId));
}
if ($deployment->getAttribute('resourceType', '') === 'functions') {
$type = 'function';
} elseif ($deployment->getAttribute('resourceType', '') === 'sites') {
$type = 'site';
}
if ($deployment->isEmpty()) {
$resourceType = $rule->getAttribute('deploymentResourceType', '');
$resourceId = $rule->getAttribute('deploymentResourceId', '');
@@ -215,6 +209,14 @@ function router(Http $utopia, Database $dbForPlatform, callable $getProjectDB, S
throw $exception;
}
if ($deployment->getAttribute('resourceType', '') === 'functions') {
$type = 'function';
} elseif ($deployment->getAttribute('resourceType', '') === 'sites') {
$type = 'site';
} else {
throw new AppwriteException(AppwriteException::GENERAL_SERVER_ERROR, 'Unknown deployment resource type', view: $errorView);
}
$resource = $type === 'function' ?
$authorization->skip(fn () => $dbForProject->getDocument('functions', $deployment->getAttribute('resourceId', ''))) :
$authorization->skip(fn () => $dbForProject->getDocument('sites', $deployment->getAttribute('resourceId', '')));
@@ -302,13 +304,13 @@ function router(Http $utopia, Database $dbForPlatform, callable $getProjectDB, S
}
}
$body = $swooleRequest->getContent() ?? '';
$body = $swooleRequest->getContent() ?: '';
$method = $swooleRequest->server['request_method'];
$requestHeaders = $request->getHeaders();
if ($resource->isEmpty() || !$resource->getAttribute('enabled')) {
if ($type === 'functions') {
if ($type === 'function') {
throw new AppwriteException(AppwriteException::FUNCTION_NOT_FOUND, view: $errorView);
} else {
throw new AppwriteException(AppwriteException::SITE_NOT_FOUND, view: $errorView);
@@ -330,7 +332,6 @@ function router(Http $utopia, Database $dbForPlatform, callable $getProjectDB, S
$runtime = match ($type) {
'function' => $runtimes[$resource->getAttribute('runtime')] ?? null,
'site' => $runtimes[$resource->getAttribute('buildRuntime')] ?? null,
default => null
};
// Static site enforced runtime
@@ -459,10 +460,10 @@ function router(Http $utopia, Database $dbForPlatform, callable $getProjectDB, S
// V2 vars
if ($version === 'v2') {
$vars = \array_merge($vars, [
'APPWRITE_FUNCTION_TRIGGER' => $headers['x-appwrite-trigger'] ?? '',
'APPWRITE_FUNCTION_TRIGGER' => $headers['x-appwrite-trigger'],
'APPWRITE_FUNCTION_DATA' => $body,
'APPWRITE_FUNCTION_USER_ID' => $headers['x-appwrite-user-id'] ?? '',
'APPWRITE_FUNCTION_JWT' => $headers['x-appwrite-user-jwt'] ?? ''
'APPWRITE_FUNCTION_USER_ID' => $headers['x-appwrite-user-id'],
'APPWRITE_FUNCTION_JWT' => $headers['x-appwrite-user-jwt']
]);
}
@@ -678,9 +679,8 @@ function router(Http $utopia, Database $dbForPlatform, callable $getProjectDB, S
if (\is_string($logs) && \strlen($logs) > $maxLogLength) {
$warningMessage = "[WARNING] Logs truncated. The output exceeded {$maxLogLength} characters.\n";
$warningLength = \strlen($warningMessage);
$maxContentLength = max(0, $maxLogLength - $warningLength);
$logs = $warningMessage . ($maxContentLength > 0 ? \substr($logs, -$maxContentLength) : '');
$maxContentLength = $maxLogLength - \strlen($warningMessage);
$logs = $warningMessage . \substr($logs, -$maxContentLength);
}
// Truncate errors if they exceed the limit
@@ -689,9 +689,8 @@ function router(Http $utopia, Database $dbForPlatform, callable $getProjectDB, S
if (\is_string($errors) && \strlen($errors) > $maxErrorLength) {
$warningMessage = "[WARNING] Errors truncated. The output exceeded {$maxErrorLength} characters.\n";
$warningLength = \strlen($warningMessage);
$maxContentLength = max(0, $maxErrorLength - $warningLength);
$errors = $warningMessage . ($maxContentLength > 0 ? \substr($errors, -$maxContentLength) : '');
$maxContentLength = $maxErrorLength - \strlen($warningMessage);
$errors = $warningMessage . \substr($errors, -$maxContentLength);
}
/** Update execution status */
$status = $executionResponse['statusCode'] >= 500 ? 'failed' : 'completed';
@@ -719,14 +718,12 @@ function router(Http $utopia, Database $dbForPlatform, callable $getProjectDB, S
throw $th;
}
} finally {
if ($type === 'function' || $type === 'site') {
$bus->dispatch(new ExecutionCompleted(
execution: $execution->getArrayCopy(),
project: $project->getArrayCopy(),
spec: $spec,
resource: $resource->getArrayCopy(),
));
}
$bus->dispatch(new ExecutionCompleted(
execution: $execution->getArrayCopy(),
project: $project->getArrayCopy(),
spec: $spec,
resource: $resource->getArrayCopy(),
));
}
$execution->setAttribute('logs', '');
@@ -774,20 +771,6 @@ function router(Http $utopia, Database $dbForPlatform, callable $getProjectDB, S
deployment: $deployment->getArrayCopy(),
));
/* cleanup */
if ($executionsRetentionCount > 0 && ENABLE_EXECUTIONS_LIMIT_ON_ROUTE) {
$resourceType = $type === 'function'
? RESOURCE_TYPE_FUNCTIONS
: RESOURCE_TYPE_SITES;
$queueForDeletes
->setProject($project)
->setResourceType($resourceType)
->setResource($resource->getSequence())
->setType(DELETE_TYPE_EXECUTIONS_LIMIT)
->trigger();
}
return true;
} elseif ($type === 'api') {
return false;
@@ -852,7 +835,7 @@ Http::init()
/*
* Appwrite Router
*/
$hostname = $request->getHostname() ?? '';
$hostname = $request->getHostname();
$platformHostnames = $platform['hostnames'] ?? [];
// Only run Router when external domain
if (!\in_array($hostname, $platformHostnames) || !empty($previewHostname)) {
@@ -1499,9 +1482,9 @@ Http::error()
->setParam('development', Http::isDevelopment())
->setParam('projectName', $project->getAttribute('name'))
->setParam('projectURL', $project->getAttribute('url'))
->setParam('message', $output['message'] ?? '')
->setParam('type', $output['type'] ?? '')
->setParam('code', $output['code'] ?? '')
->setParam('message', $output['message'])
->setParam('type', $output['type'])
->setParam('code', $output['code'])
->setParam('trace', $output['trace'] ?? [])
->setParam('exception', $error);
@@ -1616,7 +1599,7 @@ Http::get('/.well-known/acme-challenge/*')
throw new AppwriteException(AppwriteException::GENERAL_ROUTE_NOT_FOUND, 'Unknown path');
}
if (!\substr($absolute, 0, \strlen($base)) === $base) {
if (\substr($absolute, 0, \strlen($base)) !== $base) {
throw new AppwriteException(AppwriteException::GENERAL_UNAUTHORIZED_SCOPE, 'Invalid path');
}
@@ -1695,7 +1678,7 @@ Http::get('/_appwrite/authorize')
->inject('previewHostname')
->action(function (Request $request, Response $response, string $previewHostname) {
$host = $request->getHostname() ?? '';
$host = $request->getHostname();
if (!empty($previewHostname)) {
$host = $previewHostname;
}
+1 -1
View File
@@ -251,7 +251,7 @@ Http::get('/v1/mock/github/callback')
$privateKey = System::getEnv('_APP_VCS_GITHUB_PRIVATE_KEY');
$githubAppId = System::getEnv('_APP_VCS_GITHUB_APP_ID');
$github->initializeVariables($providerInstallationId, $privateKey, $githubAppId);
$owner = $github->getOwnerName($providerInstallationId) ?? '';
$owner = $github->getOwnerName($providerInstallationId);
$projectInternalId = $project->getSequence();
+7 -11
View File
@@ -46,7 +46,7 @@ use Utopia\Validator\WhiteList;
$parseLabel = function (string $label, array $responsePayload, array $requestParams, User $user) {
preg_match_all('/{(.*?)}/', $label, $matches);
foreach ($matches[1] ?? [] as $pos => $match) {
foreach ($matches[1] as $pos => $match) {
$find = $matches[0][$pos];
$parts = explode('.', $match);
@@ -54,8 +54,8 @@ $parseLabel = function (string $label, array $responsePayload, array $requestPar
throw new Exception(Exception::GENERAL_SERVER_ERROR, "The server encountered an error while parsing the label: $label. Please create an issue on GitHub to allow us to investigate further https://github.com/appwrite/appwrite/issues/new/choose");
}
$namespace = $parts[0] ?? '';
$replace = $parts[1] ?? '';
$namespace = $parts[0];
$replace = $parts[1];
$params = match ($namespace) {
'user' => (array) $user,
@@ -263,8 +263,7 @@ Http::init()
$userClone->setAttribute('type', match ($apiKey->getType()) {
API_KEY_STANDARD => ACTIVITY_TYPE_KEY_PROJECT,
API_KEY_ACCOUNT => ACTIVITY_TYPE_KEY_ACCOUNT,
API_KEY_ORGANIZATION => ACTIVITY_TYPE_KEY_ORGANIZATION,
default => ACTIVITY_TYPE_KEY_PROJECT,
default => ACTIVITY_TYPE_KEY_ORGANIZATION,
});
$auditContext->user = $userClone;
}
@@ -385,7 +384,7 @@ Http::init()
}
// Step 6: Update project and user last activity
if (! $project->isEmpty() && $project->getId() !== 'console') {
if ($project->getId() !== 'console') {
$accessedAt = $project->getAttribute('accessedAt', 0);
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_PROJECT_ACCESS)) > $accessedAt) {
$authorization->skip(fn () => $dbForPlatform->updateDocument('projects', $project->getId(), new Document([
@@ -415,9 +414,6 @@ Http::init()
}
// Steps 7-9: Access Control - Method, Namespace and Scope Validation
/**
* @var ?Method $method
*/
$method = $route->getLabel('sdk', false);
// Take the first method if there's more than one,
@@ -646,7 +642,7 @@ Http::init()
if (! empty($data) && ! $cacheLog->isEmpty()) {
$parts = explode('/', $cacheLog->getAttribute('resourceType', ''));
$type = $parts[0] ?? null;
$type = $parts[0];
if ($type === 'bucket' && (! $isImageTransformation || ! $isDisabled)) {
$bucketId = $parts[1] ?? null;
@@ -937,7 +933,7 @@ Http::shutdown()
}
$auditUser = $auditContext->user;
if (! empty($auditContext->resource) && ! \is_null($auditUser) && ! $auditUser->isEmpty()) {
if (! empty($auditContext->resource) && ! $auditUser->isEmpty()) {
/**
* audits.payload is switched to default true
* in order to auto audit payload for all endpoints
+2 -2
View File
@@ -85,7 +85,7 @@ return function (Container $container): void {
return $dbForPlatform->findOne('rules', [
Query::equal('domain', [$domain]),
]) ?? new Document();
]);
});
$permitsCurrentProject = $rule->getAttribute('projectInternalId', '') === $project->getSequence();
@@ -139,7 +139,7 @@ return function (Container $container): void {
$sdkValidator = new WhiteList($servers, true);
$sdk = \strtolower($request->getHeader('x-sdk-name', 'UNKNOWN'));
if ($sdk !== 'UNKNOWN' && $sdkValidator->isValid($sdk)) {
if ($sdk !== 'unknown' && $sdkValidator->isValid($sdk)) {
$sdks = $key->getAttribute('sdks', []);
if (!\in_array($sdk, $sdks, true)) {
+6 -7
View File
@@ -71,7 +71,7 @@ $register->set('logger', function () {
$providerConfig = match ($providerName) {
'sentry' => [ 'key' => $configChunks[0], 'projectId' => $configChunks[1] ?? '', 'host' => '',],
'logowl' => ['ticket' => $configChunks[0] ?? '', 'host' => ''],
'logowl' => ['ticket' => $configChunks[0], 'host' => ''],
default => ['key' => $providerConfig],
};
}
@@ -249,11 +249,11 @@ $register->set('pools', function () {
$poolSize = max(1, (int)($instanceConnections / $workerCount));
foreach ($connections as $key => $connection) {
$type = $connection['type'] ?? '';
$multiple = $connection['multiple'] ?? false;
$schemes = $connection['schemes'] ?? [];
$type = $connection['type'];
$multiple = $connection['multiple'];
$schemes = $connection['schemes'];
$config = [];
$dsns = explode(',', $connection['dsns'] ?? '');
$dsns = explode(',', $connection['dsns']);
foreach ($dsns as &$dsn) {
$dsn = explode('=', $dsn);
$name = ($multiple) ? $key . '_' . $dsn[0] : $key;
@@ -318,7 +318,7 @@ $register->set('pools', function () {
));
});
},
'redis' => function () use ($dsnHost, $dsnPort, $dsnPass) {
default => function () use ($dsnHost, $dsnPort, $dsnPass) {
$redis = new \Redis();
@$redis->pconnect($dsnHost, (int)$dsnPort);
if ($dsnPass) {
@@ -328,7 +328,6 @@ $register->set('pools', function () {
return $redis;
},
default => throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Invalid scheme'),
};
$poolAdapter = System::getEnv('_APP_POOL_ADAPTER', default: 'stack') === 'swoole' ? new SwoolePool() : new StackPool();
+1 -1
View File
@@ -266,7 +266,7 @@ function getDevice(string $root, string $connection = ''): Device
return new Local($root);
}
} else {
switch (strtolower(System::getEnv('_APP_STORAGE_DEVICE', Storage::DEVICE_LOCAL) ?? '')) {
switch (strtolower(System::getEnv('_APP_STORAGE_DEVICE', Storage::DEVICE_LOCAL))) {
case Storage::DEVICE_LOCAL:
default:
return new Local($root);
+4 -8
View File
@@ -375,7 +375,7 @@ return function (Container $container): void {
return $dbForPlatform->findOne('rules', [
Query::equal('domain', [$domain]),
]) ?? new Document();
]);
});
$permitsCurrentProject = $rule->getAttribute('projectInternalId', '') === $project->getSequence();
@@ -478,14 +478,10 @@ return function (Container $container): void {
}
// Get fallback session from old clients (no SameSite support) or clients who block 3rd-party cookies
if ($response) { // if in http context - add debug header
$response->addHeader('X-Debug-Fallback', 'false');
}
$response->addHeader('X-Debug-Fallback', 'false');
if (empty($store->getProperty('id', '')) && empty($store->getProperty('secret', ''))) {
if ($response) {
$response->addHeader('X-Debug-Fallback', 'true');
}
$response->addHeader('X-Debug-Fallback', 'true');
$fallback = $request->getHeader('x-fallback-cookies', '');
$fallback = \json_decode($fallback, true);
$store->decode(((is_array($fallback) && isset($fallback[$store->getKey()])) ? $fallback[$store->getKey()] : ''));
@@ -1084,7 +1080,7 @@ return function (Container $container): void {
$sdkValidator = new WhiteList($servers, true);
$sdk = \strtolower($request->getHeader('x-sdk-name', 'UNKNOWN'));
if ($sdk !== 'UNKNOWN' && $sdkValidator->isValid($sdk)) {
if ($sdk !== 'unknown' && $sdkValidator->isValid($sdk)) {
$sdks = $key->getAttribute('sdks', []);
if (! in_array($sdk, $sdks)) {
+1 -1
View File
@@ -368,7 +368,7 @@ return function (Container $container): void {
$log->addTag('code', $error->getCode());
$log->addTag('verboseType', \get_class($error));
$log->addTag('projectId', $project->getId() ?? '');
$log->addTag('projectId', $project->getId());
$log->addExtra('file', $error->getFile());
$log->addExtra('line', $error->getLine());
+1 -1
View File
@@ -129,7 +129,7 @@ $worker
$log->setAction('appwrite-queue-' . $queueName);
$log->addTag('verboseType', get_class($error));
$log->addTag('code', $error->getCode());
$log->addTag('projectId', $project->getId() ?? 'n/a');
$log->addTag('projectId', $project->getId());
$log->addExtra('file', $error->getFile());
$log->addExtra('line', $error->getLine());
$log->addExtra('trace', $error->getTraceAsString());
+1 -1
View File
@@ -1,5 +1,5 @@
parameters:
level: 3
level: 4
tmpDir: .phpstan-cache
paths:
- src
-5
View File
@@ -11,11 +11,6 @@ class Etsy extends OAuth2
*/
private string $endpoint = 'https://api.etsy.com/v3/public';
/**
* @var string
*/
private string $version = '2022-07-14';
/**
* @var array
*/
+1 -1
View File
@@ -121,7 +121,7 @@ class Podio extends OAuth2
{
$user = $this->getUser($accessToken);
return \strval($user['user_id']) ?? '';
return \strval($user['user_id']);
}
/**
-5
View File
@@ -11,11 +11,6 @@ class Zoom extends OAuth2
*/
private string $endpoint = 'https://zoom.us';
/**
* @var string
*/
private string $version = '2022-03-26';
/**
* @var array
*/
+1 -1
View File
@@ -59,7 +59,7 @@ class PersonalData extends Password
return false;
}
if ($this->email && strpos($password, explode('@', $this->email)[0] ?? '') !== false) {
if ($this->email && strpos($password, explode('@', $this->email)[0]) !== false) {
return false;
}
+1 -1
View File
@@ -21,7 +21,7 @@ class Service
array_walk($ports, function (&$value, $key) {
$split = explode(':', $value);
$this->service['ports'][
(isset($split[0])) ? $split[0] : ''
$split[0]
] = (isset($split[1])) ? $split[1] : '';
});
+1 -1
View File
@@ -15,7 +15,7 @@ class Env
foreach ($data as &$row) {
$row = explode('=', $row, 2);
$key = (isset($row[0])) ? trim($row[0]) : null;
$key = trim($row[0]);
$value = (isset($row[1])) ? (function (string $v): string {
$v = trim($v);
if (
+1 -1
View File
@@ -459,7 +459,7 @@ class Event
/**
* Identify all sections of the pattern.
*/
$type = $parts[0] ?? false;
$type = $parts[0];
$resource = $parts[1] ?? false;
$hasSubResource = $count > 3 && \str_starts_with($parts[3], '[');
$hasSubSubResource = $count > 5 && \str_starts_with($parts[5], '[') && $hasSubResource;
+1 -4
View File
@@ -44,7 +44,7 @@ class Event extends Validator
/**
* Identify all sections of the pattern.
*/
$type = $parts[0] ?? false;
$type = $parts[0];
$resource = $parts[1] ?? false;
$hasSubResource = $count > 3 && ($events[$type]['$resource'] ?? false) && ($events[$type][$parts[2]]['$resource'] ?? false);
$hasSubSubResource = $count > 5 && $hasSubResource && ($events[$type][$parts[2]][$parts[4]]['$resource'] ?? false);
@@ -61,9 +61,6 @@ class Event extends Validator
if ($hasSubSubResource) {
$subSubType = $parts[4];
$subSubResource = $parts[5];
if ($count === 8) {
$attribute = $parts[7];
}
}
if ($hasSubResource && !$hasSubSubResource) {
+1 -1
View File
@@ -24,7 +24,7 @@ class Webhook extends Event
public function trimPayload(): array
{
$trimmed = parent::trimPayload();
if (isset($this->context)) {
if (!empty($this->context)) {
$trimmed['context'] = [];
}
return $trimmed;
+10 -16
View File
@@ -91,26 +91,20 @@ class Mapper
}
}
$responses = $method->getResponses() ?? [];
$responses = $method->getResponses();
// If responses is an array, map each response to its model
if (\is_array($responses)) {
$models = [];
foreach ($responses as $response) {
$modelName = $response->getModel();
// Map each response to its model
$models = [];
foreach ($responses as $response) {
$modelName = $response->getModel();
if (\is_array($modelName)) {
foreach ($modelName as $name) {
$models[] = self::$models[$name];
}
} else {
$models[] = self::$models[$modelName];
if (\is_array($modelName)) {
foreach ($modelName as $name) {
$models[] = self::$models[$name];
}
} else {
$models[] = self::$models[$modelName];
}
} else {
// If single response, get its model and wrap in array
$modelName = $responses->getModel();
$models = [self::$models[$modelName]];
}
foreach ($models as $model) {
+1 -8
View File
@@ -370,15 +370,8 @@ class Realtime extends MessagingAdapter
$params = $getQueryParam($paramKey);
if (\array_key_exists($paramKey, $reservedParamExpectedTypes) && $params !== null) {
$expectedType = $reservedParamExpectedTypes[$paramKey];
$isExpectedType = match ($expectedType) {
'array' => \is_array($params),
'string' => \is_string($params),
default => false,
};
// If the value matches the expected type dont use it the queries
if ($isExpectedType) {
if (\is_string($params)) {
$params = null;
}
}
+1 -1
View File
@@ -16,7 +16,7 @@ class OpenSSL
* @param string $aad
* @param int $tag_length
*
* @return string
* @return string|false
*/
public static function encrypt($data, $method, $key, $options = 0, $iv = '', ?string &$tag = null, $aad = '', $tag_length = 16)
{
@@ -240,9 +240,7 @@ class Install extends Action
$inputValue = trim($inputValue);
}
if ($storedValue !== $inputValue) {
if ($installId !== '') {
$state->updateGlobalLock($installId, Server::STATUS_ERROR);
}
$state->updateGlobalLock($installId, Server::STATUS_ERROR);
$this->sendBadRequest($response, $swooleResponse, $wantsStream, 'Installation payload mismatch');
return;
}
@@ -262,16 +260,12 @@ class Install extends Action
$incomingHash = $state->hashSensitiveValue($incomingValue);
if (isset($stored[$hashField])) {
if (!hash_equals((string) $stored[$hashField], $incomingHash)) {
if ($installId !== '') {
$state->updateGlobalLock($installId, Server::STATUS_ERROR);
}
$state->updateGlobalLock($installId, Server::STATUS_ERROR);
$this->sendBadRequest($response, $swooleResponse, $wantsStream, 'Installation payload mismatch');
return;
}
} elseif (isset($stored[$field]) && $incomingValue !== '' && (string) $stored[$field] !== $incomingValue) {
if ($installId !== '') {
$state->updateGlobalLock($installId, Server::STATUS_ERROR);
}
$state->updateGlobalLock($installId, Server::STATUS_ERROR);
$this->sendBadRequest($response, $swooleResponse, $wantsStream, 'Installation payload mismatch');
return;
}
@@ -430,7 +424,7 @@ class Install extends Action
private function deriveNameFromEmail(string $email): string
{
$parts = explode('@', $email);
$username = $parts[0] ?? '';
$username = $parts[0];
$cleaned = preg_replace('/[^a-zA-Z0-9]/', '', $username);
return ucfirst($cleaned);
}
@@ -45,7 +45,7 @@ class Status extends Action
}
$data = $state->readProgressFile($installId);
if (is_array($data) && isset($data['payload']) && is_array($data['payload'])) {
if (isset($data['payload']) && is_array($data['payload'])) {
unset(
$data['payload']['opensslKey'],
$data['payload']['assistantOpenAIKey'],
@@ -54,7 +54,7 @@ class Status extends Action
);
}
// Strip sensitive data from step details
if (is_array($data) && isset($data['details']) && is_array($data['details'])) {
if (isset($data['details']) && is_array($data['details'])) {
foreach ($data['details'] as $stepKey => &$stepDetails) {
if (is_array($stepDetails)) {
unset($stepDetails['sessionSecret'], $stepDetails['trace']);
@@ -222,7 +222,7 @@ final class Config
*/
public function setEnabledDatabases(array $value): void
{
$filtered = array_values(array_filter($value, fn ($v) => is_string($v) && $v !== ''));
$filtered = array_values(array_filter($value, fn ($v) => $v !== ''));
if (!empty($filtered)) {
$this->enabledDatabases = $filtered;
}
@@ -19,13 +19,11 @@ class State
private const int PORT_MIN = 1;
private const int PORT_MAX = 65535;
private array $paths;
private bool $bootstrapped = false;
private int $lastStaleLockClearAt = 0;
public function __construct(array $paths)
public function __construct()
{
$this->paths = $paths;
}
public function buildConfig(array $overrides = [], bool $useEnv = true): Config
@@ -180,7 +178,7 @@ class State
if (!preg_match(self::PATTERN_IPV6_WITH_PORT, $value, $matches)) {
return false;
}
$host = $matches[1] ?? '';
$host = $matches[1];
$port = $matches[2] ?? null;
} else {
$parts = explode(':', $value);
+1 -1
View File
@@ -60,7 +60,7 @@ class Server
{
$this->initPaths();
$this->state = new State($this->paths);
$this->state = new State();
if (PHP_SAPI === 'cli') {
$this->runCli();
@@ -47,7 +47,7 @@ class AppDomain extends Validator
if (!preg_match(self::PATTERN_IPV6_WITH_PORT, $value, $matches)) {
return false;
}
$host = $matches[1] ?? '';
$host = $matches[1];
$port = $matches[2] ?? null;
} else {
$parts = explode(':', $value);
@@ -86,10 +86,10 @@ class Get extends Action
}
if (!$isEmployee && !empty($githubName)) {
$employeeGitHub = \array_search(\strtolower($githubName), \array_map(fn ($employee) => \strtolower($employee['gitHub']) ?? '', $employees));
$employeeGitHub = \array_search(\strtolower($githubName), \array_map(fn ($employee) => \strtolower($employee['gitHub'] ?? ''), $employees));
if (!empty($employeeGitHub)) {
$isEmployee = true;
$employeeNumber = $isEmployee ? $employees[$employeeGitHub]['spot'] : '';
$employeeNumber = $employees[$employeeGitHub]['spot'];
$createdAt = new \DateTime($employees[$employeeGitHub]['memberSince'] ?? '');
}
}
@@ -90,10 +90,10 @@ class Get extends Action
}
if (!$isEmployee && !empty($githubName)) {
$employeeGitHub = \array_search(\strtolower($githubName), \array_map(fn ($employee) => \strtolower($employee['gitHub']) ?? '', $employees));
$employeeGitHub = \array_search(\strtolower($githubName), \array_map(fn ($employee) => \strtolower($employee['gitHub'] ?? ''), $employees));
if (!empty($employeeGitHub)) {
$isEmployee = true;
$employeeNumber = $isEmployee ? $employees[$employeeGitHub]['spot'] : '';
$employeeNumber = $employees[$employeeGitHub]['spot'];
$createdAt = new \DateTime($employees[$employeeGitHub]['memberSince'] ?? '');
}
}
@@ -98,7 +98,7 @@ class Get extends Action
$doc->strictErrorChecking = false;
@$doc->loadHTML($res->getBody());
$links = $doc->getElementsByTagName('link') ?? [];
$links = $doc->getElementsByTagName('link');
$outputHref = '';
$outputExt = '';
$space = 0;
@@ -128,7 +128,7 @@ class Get extends Action
case 'jpeg':
$size = \explode('x', \strtolower($sizes));
$sizeWidth = (int) ($size[0] ?? 0);
$sizeWidth = (int) $size[0];
$sizeHeight = (int) ($size[1] ?? 0);
if (($sizeWidth * $sizeHeight) >= $space) {
@@ -60,7 +60,6 @@ class Get extends Action
public function action(string $text, int $size, int $margin, bool $download, Response $response)
{
$download = ($download === '1' || $download === 'true' || $download === 1 || $download === true);
$options = new QROptions([
'addQuietzone' => true,
'quietzoneSize' => $margin,
@@ -105,7 +105,7 @@ class Get extends Action
$client->addHeader('content-type', Client::CONTENT_TYPE_APPLICATION_JSON);
// Convert indexed array to empty array (should not happen due to Assoc validator)
if (is_array($headers) && count($headers) > 0 && array_keys($headers) === range(0, count($headers) - 1)) {
if (count($headers) > 0 && array_keys($headers) === range(0, count($headers) - 1)) {
$headers = [];
}
@@ -68,7 +68,7 @@ class Base extends Action
$owner = $github->getOwnerName($providerInstallationId);
$providerRepositoryId = $function->getAttribute('providerRepositoryId', '');
try {
$repositoryName = $github->getRepositoryName($providerRepositoryId) ?? '';
$repositoryName = $github->getRepositoryName($providerRepositoryId);
if (empty($repositoryName)) {
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
}
@@ -169,7 +169,7 @@ class Base extends Action
$owner = $github->getOwnerName($providerInstallationId);
$providerRepositoryId = $site->getAttribute('providerRepositoryId', '');
try {
$repositoryName = $github->getRepositoryName($providerRepositoryId) ?? '';
$repositoryName = $github->getRepositoryName($providerRepositoryId);
if (empty($repositoryName)) {
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
}
@@ -12,7 +12,7 @@ abstract class Action extends DatabasesAction
/**
* The current API context (either 'table' or 'collection').
*/
private ?string $context = COLLECTIONS;
private string $context = COLLECTIONS;
/**
* Get the response model used in the SDK and HTTP responses.
@@ -26,9 +26,9 @@ use Utopia\Validator\Range;
abstract class Action extends UtopiaAction
{
/**
* @var string|null The current context (either 'column' or 'attribute')
* @var string The current context (either 'column' or 'attribute')
*/
private ?string $context = ATTRIBUTES;
private string $context = ATTRIBUTES;
/**
* Get the correct response model.
@@ -14,10 +14,10 @@ use Utopia\Database\Validator\Authorization;
abstract class Action extends DatabasesAction
{
/**
* @var string|null The current context (either 'row' or 'document')
* @var string The current context (either 'row' or 'document')
*/
private ?string $context = DOCUMENTS;
private ?string $databaseType = DATABASE_TYPE_LEGACY;
private string $context = DOCUMENTS;
private string $databaseType = DATABASE_TYPE_LEGACY;
/**
* Get the response model used in the SDK and HTTP responses.
@@ -293,16 +293,6 @@ class Create extends Action
throw new Exception(Exception::USER_UNAUTHORIZED, $authorization->getDescription());
}
if ($permission === Database::PERMISSION_UPDATE) {
$validDocument = $authorization->isValid(
new Input($permission, $document->getUpdate())
);
$valid = $validCollection || $validDocument;
if ($documentSecurity && !$valid) {
throw new Exception(Exception::USER_UNAUTHORIZED, $authorization->getDescription());
}
}
$relationships = \array_filter(
$collection->getAttribute('attributes', []),
fn ($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP
@@ -100,7 +100,7 @@ class Get extends Action
}
try {
$selects = Query::groupByType($queries)['selections'] ?? [];
$selects = Query::groupByType($queries)['selections'];
$collectionTableId = 'database_' . $database->getSequence() . '_collection_' . $collection->getSequence();
$collectionTableId = 'database_' . $database->getSequence() . '_collection_' . $collection->getSequence();
@@ -353,12 +353,7 @@ class Upsert extends Action
$collectionsCache = [];
if (empty($upserted[0])) {
if ($transactionId !== null) {
// For transactions, get the document with transaction changes applied
$upserted[0] = $transactionState->getDocument($database, $collectionTableId, $documentId, $transactionId);
} else {
$upserted[0] = $dbForDatabases->getDocument($collectionTableId, $documentId);
}
$upserted[0] = $dbForDatabases->getDocument($collectionTableId, $documentId);
}
$document = $upserted[0];
@@ -127,7 +127,7 @@ class XList extends Action
}
try {
$hasSelects = ! empty(Query::groupByType($queries)['selections'] ?? []);
$hasSelects = ! empty(Query::groupByType($queries)['selections']);
$collectionTableId = 'database_' . $database->getSequence() . '_collection_' . $collection->getSequence();
// When there are no select queries, relationship loading is skipped on the
// underlying find() to avoid pulling related documents the caller did not ask for.
@@ -10,7 +10,7 @@ abstract class Action extends UtopiaAction
/**
* The current API context (either 'columnIndex' or 'index').
*/
private ?string $context = INDEX;
private string $context = INDEX;
/**
* Get the response model used in the SDK and HTTP responses.
@@ -119,6 +119,7 @@ class Get extends Action
$format = match ($days['period']) {
'1h' => 'Y-m-d\TH:00:00.000P',
'1d' => 'Y-m-d\T00:00:00.000P',
default => throw new \LogicException('Unexpected period: ' . $days['period']),
};
foreach ($metrics as $metric) {
@@ -2,6 +2,7 @@
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Logs;
use Appwrite\Detector\Detector;
use Appwrite\Extend\Exception;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
@@ -9,7 +10,6 @@ use Appwrite\SDK\Deprecated;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response as UtopiaResponse;
use DeviceDetector\DeviceDetector as Detector;
use MaxMind\Db\Reader;
use Utopia\Audit\Audit;
use Utopia\Database\Database;
@@ -103,9 +103,9 @@ class XList extends Action
$os = $detector->getOS();
$client = $detector->getClient();
$device = $detector->getDevice();
$deviceName = \is_array($device) ? ($device['deviceName'] ?? '') : '';
$deviceBrand = \is_array($device) ? ($device['deviceBrand'] ?? '') : '';
$deviceModel = \is_array($device) ? ($device['deviceModel'] ?? '') : '';
$deviceName = $device['deviceName'] ?? '';
$deviceBrand = $device['deviceBrand'] ?? '';
$deviceModel = $device['deviceModel'] ?? '';
$output[$i] = new Document([
'event' => $log['event'],
@@ -9,8 +9,8 @@ abstract class Action extends DatabasesAction
/**
* The current API context (either 'table' or 'collection').
*/
private ?string $context = COLLECTIONS;
private ?string $databaseType = LEGACY;
private string $context = COLLECTIONS;
private string $databaseType = LEGACY;
public function getDatabaseType(): string
{
@@ -144,6 +144,7 @@ class Get extends Action
$format = match ($days['period']) {
'1h' => 'Y-m-d\TH:00:00.000P',
'1d' => 'Y-m-d\T00:00:00.000P',
default => throw new \LogicException('Unexpected period: ' . $days['period']),
};
foreach ($metrics as $metric) {
@@ -133,6 +133,7 @@ class XList extends Action
$format = match ($days['period']) {
'1h' => 'Y-m-d\TH:00:00.000P',
'1d' => 'Y-m-d\T00:00:00.000P',
default => throw new \LogicException('Unexpected period: ' . $days['period']),
};
foreach ($metrics as $metric) {
@@ -2,13 +2,13 @@
namespace Appwrite\Platform\Modules\Databases\Http\TablesDB\Logs;
use Appwrite\Detector\Detector;
use Appwrite\Extend\Exception;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response as UtopiaResponse;
use DeviceDetector\DeviceDetector as Detector;
use MaxMind\Db\Reader;
use Utopia\Audit\Audit;
use Utopia\Database\Database;
@@ -97,9 +97,9 @@ class XList extends Action
$os = $detector->getOS();
$client = $detector->getClient();
$device = $detector->getDevice();
$deviceName = \is_array($device) ? ($device['deviceName'] ?? '') : '';
$deviceBrand = \is_array($device) ? ($device['deviceBrand'] ?? '') : '';
$deviceModel = \is_array($device) ? ($device['deviceModel'] ?? '') : '';
$deviceName = $device['deviceName'] ?? '';
$deviceBrand = $device['deviceBrand'] ?? '';
$deviceModel = $device['deviceModel'] ?? '';
$output[$i] = new Document([
'event' => $log['event'],
@@ -98,7 +98,7 @@ class Create extends CreateDocumentAction
$error = '';
try {
$embedResult = $embeddingAgent->embed($text);
$embedding = $embedResult['embedding'] ?? [];
$embedding = $embedResult['embedding'];
$totalDuration += $embedResult['totalDuration'] ?? 0;
$totalTokens += $embedResult['tokensProcessed'] ?? 0;
} catch (\Exception $e) {
@@ -54,7 +54,7 @@ class Databases extends Action
*/
public function action(Message $message, Document $project, Database $dbForPlatform, Database $dbForProject, callable $getDatabasesDB, Realtime $queueForRealtime, Log $log): void
{
$payload = $message->getPayload() ?? [];
$payload = $message->getPayload();
if (empty($payload)) {
throw new Exception('Missing payload');
@@ -116,7 +116,7 @@ class XList extends Base
$grouped = Query::groupByType($queries);
$filterQueries = $grouped['filters'];
$selectQueries = $grouped['selections'] ?? [];
$selectQueries = $grouped['selections'];
try {
$results = $dbForProject->find('deployments', $queries);
@@ -3,7 +3,6 @@
namespace Appwrite\Platform\Modules\Functions\Http\Executions;
use Ahc\Jwt\JWT;
use Appwrite\Event\Delete as DeleteEvent;
use Appwrite\Event\Event;
use Appwrite\Event\Func;
use Appwrite\Extend\Exception;
@@ -101,8 +100,6 @@ class Create extends Base
->inject('executor')
->inject('platform')
->inject('authorization')
->inject('queueForDeletes')
->inject('executionsRetentionCount')
->callback($this->action(...));
}
@@ -129,8 +126,6 @@ class Create extends Base
Executor $executor,
array $platform,
Authorization $authorization,
DeleteEvent $queueForDeletes,
int $executionsRetentionCount,
) {
$async = \strval($async) === 'true' || \strval($async) === '1';
@@ -145,21 +140,8 @@ class Create extends Base
}
}
/**
* @var array<string, mixed> $headers
*/
$assocParams = ['headers'];
foreach ($assocParams as $assocParam) {
if (!empty('headers') && !is_array($$assocParam)) {
$$assocParam = \json_decode($$assocParam, true);
}
}
$booleanParams = ['async'];
foreach ($booleanParams as $booleamParam) {
if (!empty($$booleamParam) && !is_bool($$booleamParam)) {
$$booleamParam = $$booleamParam === "true" ? true : false;
}
if (!is_array($headers)) {
$headers = \json_decode($headers, true);
}
// 'headers' validator
@@ -349,15 +331,6 @@ class Create extends Base
$execution = $authorization->skip(fn () => $dbForProject->createDocument('executions', $execution));
}
if ($executionsRetentionCount > 0 && ENABLE_EXECUTIONS_LIMIT_ON_ROUTE) {
$queueForDeletes
->setProject($project)
->setResource($function->getSequence())
->setResourceType(RESOURCE_TYPE_FUNCTIONS)
->setType(DELETE_TYPE_EXECUTIONS_LIMIT)
->trigger();
}
$response->setStatusCode(Response::STATUS_CODE_ACCEPTED);
$response->dynamic($execution, Response::MODEL_EXECUTION);
return;
@@ -370,10 +343,10 @@ class Create extends Base
// V2 vars
if ($version === 'v2') {
$vars = \array_merge($vars, [
'APPWRITE_FUNCTION_TRIGGER' => $headers['x-appwrite-trigger'] ?? '',
'APPWRITE_FUNCTION_TRIGGER' => $headers['x-appwrite-trigger'],
'APPWRITE_FUNCTION_DATA' => $body,
'APPWRITE_FUNCTION_USER_ID' => $headers['x-appwrite-user-id'] ?? '',
'APPWRITE_FUNCTION_JWT' => $headers['x-appwrite-user-jwt'] ?? ''
'APPWRITE_FUNCTION_USER_ID' => $headers['x-appwrite-user-id'],
'APPWRITE_FUNCTION_JWT' => $headers['x-appwrite-user-jwt']
]);
}
@@ -536,15 +509,6 @@ class Create extends Base
}
}
if ($executionsRetentionCount > 0 && ENABLE_EXECUTIONS_LIMIT_ON_ROUTE) {
$queueForDeletes
->setProject($project)
->setResource($function->getSequence())
->setResourceType(RESOURCE_TYPE_FUNCTIONS)
->setType(DELETE_TYPE_EXECUTIONS_LIMIT)
->trigger();
}
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->dynamic($execution, Response::MODEL_EXECUTION);
@@ -162,10 +162,6 @@ class Update extends Base
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'When connecting to VCS (Version Control System), you need to provide "installationId" and "providerBranch".');
}
if ($function->isEmpty()) {
throw new Exception(Exception::FUNCTION_NOT_FOUND);
}
if (empty($runtime)) {
$runtime = $function->getAttribute('runtime');
}
@@ -112,6 +112,7 @@ class Get extends Base
$format = match ($days['period']) {
'1h' => 'Y-m-d\TH:00:00.000P',
'1d' => 'Y-m-d\T00:00:00.000P',
default => throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Unsupported period "' . $days['period'] . '".'),
};
foreach ($metrics as $metric) {
@@ -2,6 +2,7 @@
namespace Appwrite\Platform\Modules\Functions\Http\Usage;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
@@ -104,6 +105,7 @@ class XList extends Base
$format = match ($days['period']) {
'1h' => 'Y-m-d\TH:00:00.000P',
'1d' => 'Y-m-d\T00:00:00.000P',
default => throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Unsupported period "' . $days['period'] . '".'),
};
foreach ($metrics as $metric) {
@@ -77,11 +77,7 @@ class Delete extends Base
}
$variable = $dbForProject->getDocument('variables', $variableId);
if ($variable === false || $variable->isEmpty() || $variable->getAttribute('resourceInternalId') !== $function->getSequence() || $variable->getAttribute('resourceType') !== 'function') {
throw new Exception(Exception::VARIABLE_NOT_FOUND);
}
if ($variable === false || $variable->isEmpty()) {
if ($variable->isEmpty() || $variable->getAttribute('resourceInternalId') !== $function->getSequence() || $variable->getAttribute('resourceType') !== 'function') {
throw new Exception(Exception::VARIABLE_NOT_FOUND);
}
@@ -66,7 +66,6 @@ class Get extends Base
$variable = $dbForProject->getDocument('variables', $variableId);
if (
$variable === false ||
$variable->isEmpty() ||
$variable->getAttribute('resourceInternalId') !== $function->getSequence() ||
$variable->getAttribute('resourceType') !== 'function'
@@ -74,10 +73,6 @@ class Get extends Base
throw new Exception(Exception::VARIABLE_NOT_FOUND);
}
if ($variable === false || $variable->isEmpty()) {
throw new Exception(Exception::VARIABLE_NOT_FOUND);
}
$response->dynamic($variable, Response::MODEL_VARIABLE);
}
}
@@ -85,7 +85,7 @@ class Update extends Base
}
$variable = $dbForProject->getDocument('variables', $variableId);
if ($variable === false || $variable->isEmpty() || $variable->getAttribute('resourceInternalId') !== $function->getSequence() || $variable->getAttribute('resourceType') !== 'function') {
if ($variable->isEmpty() || $variable->getAttribute('resourceInternalId') !== $function->getSequence() || $variable->getAttribute('resourceType') !== 'function') {
throw new Exception(Exception::VARIABLE_NOT_FOUND);
}
@@ -102,7 +102,7 @@ class Builds extends Action
): void {
Console::log('Build action started');
$payload = $message->getPayload() ?? [];
$payload = $message->getPayload();
if (empty($payload)) {
throw new \Exception('Missing payload');
@@ -206,7 +206,7 @@ class Builds extends Action
throw new \Exception('Resource not found');
}
if ($isResourceBlocked($project, $resourceKey === 'functions' ? RESOURCE_TYPE_FUNCTIONS : RESOURCE_TYPE_SITES, $resource->getId())) {
if ($isResourceBlocked($project, $resource->getCollection() === 'functions' ? RESOURCE_TYPE_FUNCTIONS : RESOURCE_TYPE_SITES, $resource->getId())) {
throw new \Exception('Resource is blocked');
}
@@ -226,10 +226,6 @@ class Builds extends Action
$spec = Config::getParam('specifications')[$resource->getAttribute('buildSpecification', APP_COMPUTE_SPECIFICATION_DEFAULT)];
if ($resource->getCollection() === 'functions' && \is_null($runtime)) {
throw new \Exception('Runtime "' . $resource->getAttribute('runtime', '') . '" is not supported');
}
// Realtime preparation
$event = "{$resource->getCollection()}.[{$resourceKey}].deployments.[deploymentId].update";
$queueForRealtime
@@ -829,7 +825,8 @@ class Builds extends Action
Console::log('Runtime creation finished');
if ($dbForProject->getDocument('deployments', $deploymentId)->getAttribute('status') === 'canceled') {
$latestDeployment = $dbForProject->getDocument('deployments', $deploymentId);
if ($latestDeployment->getAttribute('status') === 'canceled') {
$this->cancelDeployment($deployment->getId(), $dbForProject, $queueForRealtime);
return;
@@ -1259,21 +1256,6 @@ class Builds extends Action
*/
protected function afterBuildSuccess(Realtime $queueForRealtime, Database $dbForProject, Document &$deployment, array $runtime, ?string $adapter): void
{
if (! ($queueForRealtime instanceof Realtime)) {
throw new Exception('queueForRealtime must be an instance of Realtime');
}
if (! ($dbForProject instanceof Database)) {
throw new Exception('dbForProject must be an instance of Database');
}
if (! ($deployment instanceof Document)) {
throw new Exception('deployment must be an instance of Document');
}
if (! is_array($runtime)) {
throw new Exception('runtime must be an array');
}
if (! is_string($adapter) && ! is_null($adapter)) {
throw new Exception('adapter must be a string or null');
}
}
/**
@@ -1283,13 +1265,6 @@ class Builds extends Action
Document $project,
Document $deployment,
): void {
if (! ($project instanceof Document)) {
throw new Exception('project must be an instance of Document');
}
if (! ($deployment instanceof Document)) {
throw new Exception('deployment must be an instance of Document');
}
}
protected function getRuntime(Document $resource, string $version): array
@@ -1313,6 +1288,7 @@ class Builds extends Action
return match ($resource->getCollection()) {
'functions' => $resource->getAttribute('version', 'v2'),
'sites' => 'v5',
default => throw new \Exception('Unsupported resource type "' . $resource->getCollection() . '".'),
};
}
@@ -1445,11 +1421,10 @@ class Builds extends Action
]);
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https';
$previewUrl = match ($resource->getCollection()) {
'functions' => '',
'sites' => !$rule->isEmpty() ? ("{$protocol}://" . $rule->getAttribute('domain', '')) : '',
default => throw new \Exception('Invalid resource type')
};
$previewUrl = '';
if ($resource->getCollection() === 'sites' && !$rule->isEmpty()) {
$previewUrl = "{$protocol}://" . $rule->getAttribute('domain', '');
}
$comment = new Comment($platform);
$comment->parseComment($github->getComment($owner, $repositoryName, $commentId));
@@ -57,7 +57,7 @@ class Screenshots extends Action
): void {
Console::log('Screenshot action started');
$payload = $message->getPayload() ?? [];
$payload = $message->getPayload();
if (empty($payload)) {
throw new \Exception('Missing payload');
@@ -162,7 +162,7 @@ class Screenshots extends Action
try {
$config = $configs[$key];
$config['headers'] = \array_merge($config['headers'] ?? [], [
$config['headers'] = \array_merge($config['headers'], [
'x-appwrite-key' => API_KEY_DYNAMIC . '_' . $apiKey
]);
$config['sleep'] = 3000;
@@ -82,7 +82,7 @@ class Get extends Action
}
$certificatePayload = @openssl_x509_parse($peerCertificate);
if ($certificatePayload === false || !\is_array($certificatePayload)) {
if ($certificatePayload === false) {
throw new Exception(Exception::HEALTH_INVALID_HOST, 'Failed to parse peer certificate for ' . $domain);
}
@@ -16,6 +16,7 @@ use Appwrite\Event\Publisher\Screenshot;
use Appwrite\Event\Publisher\StatsResources as StatsResourcesPublisher;
use Appwrite\Event\Publisher\Usage as UsagePublisher;
use Appwrite\Event\Webhook;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Health\Http\Health\Queue\Base;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
@@ -123,6 +124,7 @@ class Get extends Base
System::getEnv('_APP_SCREENSHOTS_QUEUE_NAME', Event::SCREENSHOTS_QUEUE_NAME) => $publisherForScreenshots,
System::getEnv('_APP_MESSAGING_QUEUE_NAME', Event::MESSAGING_QUEUE_NAME) => $queueForMessaging,
System::getEnv('_APP_MIGRATIONS_QUEUE_NAME', Event::MIGRATIONS_QUEUE_NAME) => $publisherForMigrations,
default => throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Unknown queue name: ' . $name),
};
$failed = $queue->getSize(failed: true);
@@ -63,7 +63,7 @@ class Delete extends Action
$key = $dbForPlatform->getDocument('devKeys', $keyId);
if ($key === false || $key->isEmpty() || $key->getAttribute('projectInternalId') !== $project->getSequence()) {
if ($key->isEmpty() || $key->getAttribute('projectInternalId') !== $project->getSequence()) {
throw new Exception(Exception::KEY_NOT_FOUND);
}
@@ -63,7 +63,7 @@ class Get extends Action
$key = $dbForPlatform->getDocument('devKeys', $keyId);
if ($key === false || $key->isEmpty() || $key->getAttribute('projectInternalId') !== $project->getSequence()) {
if ($key->isEmpty() || $key->getAttribute('projectInternalId') !== $project->getSequence()) {
throw new Exception(Exception::KEY_NOT_FOUND);
}
@@ -66,7 +66,7 @@ class Update extends Action
$key = $dbForPlatform->getDocument('devKeys', $keyId);
if ($key === false || $key->isEmpty() || $key->getAttribute('projectInternalId') !== $project->getSequence()) {
if ($key->isEmpty() || $key->getAttribute('projectInternalId') !== $project->getSequence()) {
throw new Exception(Exception::KEY_NOT_FOUND);
}
@@ -109,7 +109,7 @@ class XList extends Action
}
try {
$selectQueries = Query::groupByType($queries)['selections'] ?? [];
$selectQueries = Query::groupByType($queries)['selections'];
$filterQueries = Query::groupByType($queries)['filters'];
$projects = $this->find($dbForPlatform, $queries, $selectQueries);
@@ -164,9 +164,7 @@ class Action extends PlatformAction
$validator = new AnyOf($cnameValidators);
$validators[] = $validator;
if (\is_null($mainValidator)) {
$mainValidator = $validator;
}
$mainValidator = $validator;
}
// Ensure at least one of CNAME/A/AAAA record points to our servers properly
@@ -84,7 +84,8 @@ class Create extends Action
$collection = match ($resourceType) {
'site' => 'sites',
'function' => 'functions'
'function' => 'functions',
default => throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Invalid resource type: ' . $resourceType),
};
$resource = $dbForProject->getDocument($collection, $resourceId);
if ($resource->isEmpty()) {
@@ -116,7 +116,7 @@ class XList extends Base
$grouped = Query::groupByType($queries);
$filterQueries = $grouped['filters'];
$selectQueries = $grouped['selections'] ?? [];
$selectQueries = $grouped['selections'];
try {
$results = $dbForProject->find('deployments', $queries);
@@ -164,10 +164,6 @@ class Update extends Base
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'When connecting to VCS (Version Control System), you need to provide "installationId" and "providerBranch".');
}
if ($site->isEmpty()) {
throw new Exception(Exception::SITE_NOT_FOUND);
}
if (empty($framework)) {
$framework = $site->getAttribute('framework');
}
@@ -121,6 +121,7 @@ class Get extends Base
$format = match ($days['period']) {
'1h' => 'Y-m-d\TH:00:00.000P',
'1d' => 'Y-m-d\T00:00:00.000P',
default => throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Unsupported period: ' . $days['period']),
};
foreach ($metrics as $metric) {
@@ -2,6 +2,7 @@
namespace Appwrite\Platform\Modules\Sites\Http\Usage;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
@@ -107,6 +108,7 @@ class XList extends Base
$format = match ($days['period']) {
'1h' => 'Y-m-d\TH:00:00.000P',
'1d' => 'Y-m-d\T00:00:00.000P',
default => throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Unsupported period: ' . $days['period']),
};
foreach ($metrics as $metric) {
@@ -67,11 +67,7 @@ class Delete extends Base
}
$variable = $dbForProject->getDocument('variables', $variableId);
if ($variable === false || $variable->isEmpty() || $variable->getAttribute('resourceInternalId') !== $site->getSequence() || $variable->getAttribute('resourceType') !== 'site') {
throw new Exception(Exception::VARIABLE_NOT_FOUND);
}
if ($variable === false || $variable->isEmpty()) {
if ($variable->isEmpty() || $variable->getAttribute('resourceInternalId') !== $site->getSequence() || $variable->getAttribute('resourceType') !== 'site') {
throw new Exception(Exception::VARIABLE_NOT_FOUND);
}
@@ -66,7 +66,6 @@ class Get extends Base
$variable = $dbForProject->getDocument('variables', $variableId);
if (
$variable === false ||
$variable->isEmpty() ||
$variable->getAttribute('resourceInternalId') !== $site->getSequence() ||
$variable->getAttribute('resourceType') !== 'site'
@@ -74,10 +73,6 @@ class Get extends Base
throw new Exception(Exception::VARIABLE_NOT_FOUND);
}
if ($variable === false || $variable->isEmpty()) {
throw new Exception(Exception::VARIABLE_NOT_FOUND);
}
$response->dynamic($variable, Response::MODEL_VARIABLE);
}
}
@@ -79,7 +79,7 @@ class Update extends Base
}
$variable = $dbForProject->getDocument('variables', $variableId);
if ($variable === false || $variable->isEmpty() || $variable->getAttribute('resourceInternalId') !== $site->getSequence() || $variable->getAttribute('resourceType') !== 'site') {
if ($variable->isEmpty() || $variable->getAttribute('resourceInternalId') !== $site->getSequence() || $variable->getAttribute('resourceType') !== 'site') {
throw new Exception(Exception::VARIABLE_NOT_FOUND);
}
@@ -384,14 +384,11 @@ class Create extends Action
->setAttribute('chunksUploaded', $chunksUploaded);
/**
* Validate create permission and skip authorization in updateDocument
* Without this, the file creation will fail when user doesn't have update permission
* Skip authorization in updateDocument.
* Without this, the file creation will fail when user doesn't have update permission.
* However as with chunk upload even if we are updating, we are essentially creating a file
* adding it's new chunk so we validate create permission instead of update
* adding it's new chunk so we rely on the create-permission check performed earlier.
*/
if (!$authorization->isValid(new Input(Database::PERMISSION_CREATE, $bucket->getCreate()))) {
throw new Exception(Exception::USER_UNAUTHORIZED);
}
$file = $authorization->skip(fn () => $dbForProject->updateDocument('bucket_' . $bucket->getSequence(), $fileId, $file));
}
@@ -431,15 +428,11 @@ class Create extends Action
->setAttribute('metadata', $metadata);
/**
* Validate create permission and skip authorization in updateDocument
* Without this, the file creation will fail when user doesn't have update permission
* Skip authorization in updateDocument.
* Without this, the file creation will fail when user doesn't have update permission.
* However as with chunk upload even if we are updating, we are essentially creating a file
* adding it's new chunk so we validate create permission instead of update
* adding it's new chunk so we rely on the create-permission check performed earlier.
*/
if (!$authorization->isValid(new Input(Database::PERMISSION_CREATE, $bucket->getCreate()))) {
throw new Exception(Exception::USER_UNAUTHORIZED);
}
try {
$file = $authorization->skip(fn () => $dbForProject->updateDocument('bucket_' . $bucket->getSequence(), $fileId, $file));
} catch (NotFoundException) {
@@ -468,8 +461,5 @@ class Create extends Action
*/
protected function afterCreateSuccess(Document $file)
{
if (!($file instanceof Document)) {
throw new Exception('file must be an instance of document');
}
}
}
@@ -200,7 +200,7 @@ class Get extends Action
// when file extension is not provided and the mime type is not one of our supported outputs
// we fallback to `jpg` output format
$output = empty($type) ? (array_search($mime, $outputs) ?? 'jpg') : $type;
$output = empty($type) ? (array_search($mime, $outputs) ?: 'jpg') : $type;
}
$startTime = \microtime(true);
@@ -243,7 +243,7 @@ class Get extends Action
$image->crop((int) $width, (int) $height, $gravity);
if (!empty($opacity) || $opacity === 0) {
if (!empty($opacity)) {
$image->setOpacity($opacity);
}
@@ -130,7 +130,7 @@ class Update extends Action
}
if (\is_null($permissions)) {
$permissions = $file->getPermissions() ?? [];
$permissions = $file->getPermissions();
}
$file->setAttribute('$permissions', $permissions);
@@ -143,11 +143,12 @@ class XList extends Action
});
foreach ($stats as $stat) {
$bucket = $bucketByStatsId[$stat->getId()];
if ($bucket) {
$bucket->setAttribute('totalSize', $stat->getAttribute('value', 0));
if (!isset($bucketByStatsId[$stat->getId()])) {
continue;
}
$bucket = $bucketByStatsId[$stat->getId()];
$bucket->setAttribute('totalSize', $stat->getAttribute('value', 0));
}
} catch (\Throwable) {
// Stats may not be available, default to 0
@@ -109,6 +109,7 @@ class Get extends Action
$format = match ($days['period']) {
'1h' => 'Y-m-d\\TH:00:00.000P',
'1d' => 'Y-m-d\\T00:00:00.000P',
default => throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Unsupported period: ' . $days['period']),
};
foreach ($metrics as $metric) {
@@ -2,6 +2,7 @@
namespace Appwrite\Platform\Modules\Storage\Http\Usage;
use Appwrite\Extend\Exception;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
@@ -92,6 +93,7 @@ class XList extends Action
$format = match ($days['period']) {
'1h' => 'Y-m-d\\TH:00:00.000P',
'1d' => 'Y-m-d\\T00:00:00.000P',
default => throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Unsupported period: ' . $days['period']),
};
foreach ($metrics as $metric) {
@@ -104,7 +104,7 @@ class Get extends Action
$privateKey = System::getEnv('_APP_VCS_GITHUB_PRIVATE_KEY');
$githubAppId = System::getEnv('_APP_VCS_GITHUB_APP_ID');
$github->initializeVariables($providerInstallationId, $privateKey, $githubAppId);
$owner = $github->getOwnerName($providerInstallationId) ?? '';
$owner = $github->getOwnerName($providerInstallationId);
$projectInternalId = $project->getSequence();
@@ -121,11 +121,11 @@ class Get extends Action
if (!empty($code)) {
$oauth2 = new OAuth2Github(System::getEnv('_APP_VCS_GITHUB_CLIENT_ID', ''), System::getEnv('_APP_VCS_GITHUB_CLIENT_SECRET', ''), "");
$accessToken = $oauth2->getAccessToken($code) ?? '';
$refreshToken = $oauth2->getRefreshToken($code) ?? '';
$accessToken = $oauth2->getAccessToken($code);
$refreshToken = $oauth2->getRefreshToken($code);
$accessTokenExpiry = DateTime::addSeconds(new \DateTime(), \intval($oauth2->getAccessTokenExpiry($code)));
$personalSlug = $oauth2->getUserSlug($accessToken) ?? '';
$personalSlug = $oauth2->getUserSlug($accessToken);
$personal = $personalSlug === $owner;
}
@@ -107,7 +107,7 @@ trait Deployment
$activate = true;
}
$owner = $github->getOwnerName($providerInstallationId) ?? '';
$owner = $github->getOwnerName($providerInstallationId);
try {
$repositoryName = $github->getRepositoryName($providerRepositoryId);
} catch (RepositoryNotFound $e) {
@@ -59,7 +59,7 @@ class Get extends Action
) {
$installation = $dbForPlatform->getDocument('installations', $installationId);
if ($installation === false || $installation->isEmpty()) {
if ($installation->isEmpty()) {
throw new Exception(Exception::INSTALLATION_NOT_FOUND);
}
@@ -73,9 +73,9 @@ class XList extends Action
$githubAppId = System::getEnv('_APP_VCS_GITHUB_APP_ID');
$github->initializeVariables($providerInstallationId, $privateKey, $githubAppId);
$owner = $github->getOwnerName($providerInstallationId) ?? '';
$owner = $github->getOwnerName($providerInstallationId);
try {
$repositoryName = $github->getRepositoryName($providerRepositoryId) ?? '';
$repositoryName = $github->getRepositoryName($providerRepositoryId);
if (empty($repositoryName)) {
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
}
@@ -83,7 +83,7 @@ class XList extends Action
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
}
$branches = $github->listBranches($owner, $repositoryName) ?? [];
$branches = $github->listBranches($owner, $repositoryName);
$response->dynamic(new Document([
'branches' => \array_map(function ($branch) {
@@ -79,7 +79,7 @@ class Get extends Action
$owner = $github->getOwnerName($providerInstallationId);
try {
$repositoryName = $github->getRepositoryName($providerRepositoryId) ?? '';
$repositoryName = $github->getRepositoryName($providerRepositoryId);
if (empty($repositoryName)) {
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
}
@@ -152,7 +152,7 @@ class Create extends Action
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Provider Error: ' . $repository['message']);
}
$repository['id'] = \strval($repository['id']) ?? '';
$repository['id'] = \strval($repository['id']);
$repository['pushedAt'] = $repository['pushed_at'] ?? '';
$repository['organization'] = $installation->getAttribute('organization', '');
$repository['provider'] = $installation->getAttribute('provider', '');
@@ -121,7 +121,7 @@ class Create extends Action
$owner = $github->getOwnerName($providerInstallationId);
try {
$repositoryName = $github->getRepositoryName($providerRepositoryId) ?? '';
$repositoryName = $github->getRepositoryName($providerRepositoryId);
if (empty($repositoryName)) {
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
}
@@ -73,9 +73,9 @@ class Get extends Action
$githubAppId = System::getEnv('_APP_VCS_GITHUB_APP_ID');
$github->initializeVariables($providerInstallationId, $privateKey, $githubAppId);
$owner = $github->getOwnerName($providerInstallationId) ?? '';
$owner = $github->getOwnerName($providerInstallationId);
try {
$repositoryName = $github->getRepositoryName($providerRepositoryId) ?? '';
$repositoryName = $github->getRepositoryName($providerRepositoryId);
if (empty($repositoryName)) {
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
}
@@ -97,7 +97,7 @@ class Get extends Action
}
}
$repository['id'] = \strval($repository['id']) ?? '';
$repository['id'] = \strval($repository['id']);
$repository['pushedAt'] = $repository['pushed_at'] ?? '';
$repository['organization'] = $installation->getAttribute('organization', '');
$repository['provider'] = $installation->getAttribute('provider', '');
+7 -17
View File
@@ -109,7 +109,7 @@ class Install extends Action
file_put_contents($this->path . '/' . $composeFileName . '.' . $time . '.backup', $data);
$compose = new Compose($data);
$appwrite = $compose->getService('appwrite');
$oldVersion = $appwrite?->getImageVersion();
$oldVersion = $appwrite->getImageVersion();
try {
$ports = $compose->getService('traefik')->getPorts();
} catch (\Throwable $th) {
@@ -122,10 +122,6 @@ class Install extends Action
if ($oldVersion) {
foreach ($compose->getServices() as $service) {
if (!$service) {
continue;
}
$env = $service->getEnvironment()->list();
foreach ($env as $key => $value) {
@@ -177,9 +173,6 @@ class Install extends Action
// can be detected by the DB service name or _APP_DB_HOST.
$existingDatabase = null;
foreach ($compose->getServices() as $service) {
if (!$service) {
continue;
}
$svcEnv = $service->getEnvironment()->list();
if (isset($svcEnv['_APP_DB_ADAPTER'])) {
$existingDatabase = $svcEnv['_APP_DB_ADAPTER'];
@@ -229,8 +222,8 @@ class Install extends Action
$assistantExistsInOldCompose = false;
if ($existingInstallation) {
try {
$assistantService = $compose->getService('appwrite-assistant');
$assistantExistsInOldCompose = $assistantService !== null;
$compose->getService('appwrite-assistant');
$assistantExistsInOldCompose = true;
} catch (\Throwable) {
/* ignore */
}
@@ -290,7 +283,7 @@ class Install extends Action
continue;
}
if ($var['name'] === '_APP_DB_ADAPTER' && $data !== false) {
if ($var['name'] === '_APP_DB_ADAPTER' && $data !== '') {
$userInput[$var['name']] = $database;
continue;
}
@@ -334,7 +327,7 @@ class Install extends Action
@unlink(InstallerServer::INSTALLER_COMPLETE_FILE);
$state = new State([]);
$state = new State();
$state->clearStaleLock();
$installerConfig = $this->readInstallerConfig();
@@ -608,7 +601,7 @@ class Install extends Action
$this->copyMongoEntrypointIfNeeded();
}
if (!$noStart && $startIndex <= 2) {
if (!$noStart) {
$currentStep = InstallerServer::STEP_DOCKER_CONTAINERS;
$this->updateProgress($progress, InstallerServer::STEP_DOCKER_CONTAINERS, InstallerServer::STATUS_IN_PROGRESS, $messages);
$this->runDockerCompose($input, $isLocalInstall, $useExistingConfig, $isCLI, $progress, $isUpgrade);
@@ -838,7 +831,7 @@ class Install extends Action
'email' => $email,
'domain' => $domain,
'database' => $database,
'ip' => ($hostIp !== false && $hostIp !== $domain) ? $hostIp : null,
'ip' => ($hostIp !== $domain) ? $hostIp : null,
'os' => php_uname('s') . ' ' . php_uname('r'),
'arch' => php_uname('m'),
'cpus' => ((int) trim((string) \shell_exec('nproc'))) ?: null,
@@ -1365,9 +1358,6 @@ class Install extends Action
}
foreach ($compose->getServices() as $service) {
if (!$service) {
continue;
}
$env = $service->getEnvironment()->list();
$host = $env['_APP_DB_HOST'] ?? null;
if ($host !== null && in_array($host, $dbServices, true)) {
-47
View File
@@ -75,7 +75,6 @@ class Interval extends Action
protected function getTasks(): array
{
$intervalDomainVerification = (int) System::getEnv('_APP_INTERVAL_DOMAIN_VERIFICATION', '120'); // 2 minutes
$intervalCleanupStaleExecutions = (int) System::getEnv('_APP_INTERVAL_CLEANUP_STALE_EXECUTIONS', '300'); // 5 minutes
return [
[
@@ -135,50 +134,4 @@ class Interval extends Action
Span::add("interval.domainVerification.processed", $processed);
Span::add("interval.domainVerification.failed", $failed);
}
private function cleanupStaleExecutions(Database $dbForPlatform, callable $getProjectDB): void
{
$staleThreshold = DatabaseDateTime::addSeconds(new DateTime(), -1200); // 20 minutes ago
$scanned = 0;
$processed = 0;
$failed = 0;
$dbForPlatform->foreach(
'projects',
function (Document $project) use ($getProjectDB, $staleThreshold, &$scanned, &$processed, &$failed) {
try {
$dbForProject = $getProjectDB($project);
$staleExecutions = $dbForProject->find('executions', [
Query::equal('status', ['processing']),
Query::lessThan('$createdAt', $staleThreshold),
Query::limit(100),
]);
$scanned += \count($staleExecutions);
if (\count($staleExecutions) === 0) {
return;
}
foreach ($staleExecutions as $execution) {
$dbForProject->updateDocument('executions', $execution->getId(), new Document(['status' => 'failed', 'errors' => 'Execution timed out']));
}
$processed++;
} catch (\Throwable $th) {
$failed++;
}
},
[
Query::equal('region', [System::getEnv('_APP_REGION', 'default')]),
Query::limit(100),
]
);
Span::add("interval.cleanupStaleExecutions.scanned", $scanned);
Span::add("interval.cleanupStaleExecutions.processed", $processed);
Span::add("interval.cleanupStaleExecutions.failed", $failed);
}
}
+2 -2
View File
@@ -181,7 +181,7 @@ class SDKs extends Action
Console::log('');
if ($createRelease && ! $examplesOnly) {
if ($createRelease) {
Console::info("━━━ {$language['name']} SDK ({$platform['name']}, {$language['version']}) ━━━");
$changelog = $language['changelog'] ?? '';
$changelog = ($changelog) ? \file_get_contents($changelog) : '# Change Log';
@@ -1146,7 +1146,7 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
if (! empty($prListOutput[0])) {
$parts = \explode(' ', trim($prListOutput[0]), 2);
$prNumber = $parts[0] ?? '';
$prNumber = $parts[0];
$prUrl = $parts[1] ?? '';
}
}
+1 -1
View File
@@ -102,7 +102,7 @@ abstract class ScheduleBase extends Action
$this->collectSchedules($dbForPlatform, $getProjectDB, $lastSyncUpdate, $isResourceBlocked);
});
while (true) {
for (;;) {
try {
go(fn () => $this->enqueueResources($dbForPlatform, $getProjectDB));
$this->scheduleTelemetryCount->record(count($this->schedules), ['resourceType' => static::getSupportedResource()]);
@@ -21,8 +21,6 @@ class ScheduleFunctions extends ScheduleBase
public const UPDATE_TIMER = 10; // seconds
public const ENQUEUE_TIMER = 60; // seconds
private ?float $lastEnqueueUpdate = null;
public static function getName(): string
{
return 'schedule-functions';
@@ -43,7 +41,10 @@ class ScheduleFunctions extends ScheduleBase
$timerStart = \microtime(true);
$time = DateTime::now();
$enqueueDiff = $this->lastEnqueueUpdate === null ? 0 : $timerStart - $this->lastEnqueueUpdate;
// TODO: Track the last enqueue timestamp to subtract ENQUEUE_TIMER drift from
// the time frame. Previously this used $this->lastEnqueueUpdate as a property
// but enabling the assignment broke scheduling, so the diff stays 0.
$enqueueDiff = 0;
$timeFrame = DateTime::addSeconds(new \DateTime(), static::ENQUEUE_TIMER - $enqueueDiff);
Console::log("Enqueue tick: started at: $time (with diff $enqueueDiff)");
@@ -128,9 +129,6 @@ class ScheduleFunctions extends ScheduleBase
$timerEnd = \microtime(true);
// TODO: This was a bug before because it wasn't passed by reference, enabling it breaks scheduling
//$this->lastEnqueueUpdate = $timerStart;
Console::log("Enqueue tick: {$total} executions were enqueued in " . ($timerEnd - $timerStart) . " seconds");
}
}

Some files were not shown because too many files have changed in this diff Show More