Conversation
📝 WalkthroughWalkthroughThe Avatars functionality was moved from a single controller (app/controllers/api/avatars.php removed) into a new Avatars platform module. The platform now registers Appwrite\Platform\Modules\Avatars\Module and its HTTP service, and many new HTTP action classes were added (browsers, credit-cards, flags, image, favicon, qr, initials, screenshots, cards cloud/front/back/og) plus shared Action utilities for image processing, OAuth, and caching. Service config in app/config/services.php was updated to indicate avatars now use modules. Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes 🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
📜 Recent review detailsConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
💤 Files with no reviewable changes (1)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Security Scan Results for PRDocker Image Scan Results
Source Code Scan Results🎉 No vulnerabilities found! |
✨ Benchmark results
⚡ Benchmark Comparison
|
There was a problem hiding this comment.
Actionable comments posted: 13
🤖 Fix all issues with AI agents
In @app/config/services.php:
- Line 51: Delete the obsolete avatars controller file named avatars.php (the
old API avatars controller) from the codebase to complete the migration; remove
any remaining references to it (routes, service config entries, autoload lists)
and run a quick search or test suite to confirm nothing still imports or
instantiates the avatars controller.
In @src/Appwrite/Platform/Modules/Avatars/Http/Action.php:
- Around line 106-108: Fix the typo in the inline comment next to the check for
$verificationId in Action.php: change "Race codition, handeled in catch" to
"Race condition, handled in catch" near the if (empty($verificationId)) { throw
new \Exception("Locked tokens."); } block so the comment reads correctly and
references the catch handling for the race condition.
In @src/Appwrite/Platform/Modules/Avatars/Http/Cards/Cloud/Back/Get.php:
- Line 45: The parameter description for the 'width' param in the Get.php
avatars card has a double space ("Resize image width"); update the string
passed to the ->param call for 'width' (the description argument in the
builder/validator invocation) to use a single space so it reads "Resize image
width, Pass an integer between 0 to 512.".
- Around line 95-96: Remove the unnecessary setlocale(LC_ALL, "en_US.utf8");
call and delete the commented-out iconv line (// $userId = \iconv("utf-8",
"ascii//TRANSLIT", $userId);) in the Get.php cloud avatars handler so no dead
code or unintended locale side-effects remain; locate these statements near the
top of the Get class/method handling the cloud back card response and simply
remove both lines, leaving any real userId normalization logic intact if present
elsewhere.
In @src/Appwrite/Platform/Modules/Avatars/Http/Cards/Cloud/Front/Get.php:
- Around line 195-197: The truncation of $name in Get.php uses strlen() and
substr(), which can split multibyte UTF‑8 characters; replace the checks and
trimming around the $name variable (the block that currently uses \strlen($name)
and \substr($name, 0, 32)) with mb_strlen() and mb_substr() using the 'UTF-8'
encoding (or default internal encoding) so the string is measured and sliced by
characters not bytes; ensure mbstring is available or fall back safely if
needed.
In @src/Appwrite/Platform/Modules/Avatars/Http/Cards/Cloud/OG/Get.php:
- Around line 294-296: The code truncates UTF-8 names using \strlen and \substr
which can break multibyte characters; in the Get class/method handling OG Cards
(file Get.php) replace \strlen($name) and \substr($name, ...) with
\mb_strlen($name) and \mb_substr($name, ...), and update the other length checks
that compare $name length (the subsequent conditional/switch checks in the same
method) to use \mb_strlen($name) as well so truncation and comparisons are UTF-8
safe.
In @src/Appwrite/Platform/Modules/Avatars/Http/Favicon/Get.php:
- Around line 160-168: The second HTTP client call using new Client() (variable
$client) before ->fetch($outputHref) is missing the same User-Agent header used
earlier; update the chain to include ->setUserAgent(...) with the same value or
variable used in the first fetch (so the request uses a proper User-Agent),
keeping the existing
->setAllowRedirects(true)->setMaxRedirects(5)->fetch($outputHref) flow and
preserving the existing Exception::AVATAR_REMOTE_URL_FAILED behavior.
- Line 110: The current merge of parse_url($url) and parse_url($href) can
produce invalid results when $href is already absolute; update the logic in
Get.php so you first detect if $href is absolute (e.g.,
parse_url($href)['scheme'] is set or $href starts with '//') and if so use $href
(or URLParse::unparse(parse_url($href))) directly as $absolute; only when $href
is relative perform URLParse::unparse(array_merge(parse_url($url),
parse_url($href))). Keep references to URLParse::unparse, parse_url, and the
$url/$href variables when making this change.
In @src/Appwrite/Platform/Modules/Avatars/Http/Image/Get.php:
- Around line 78-85: The fetch currently disables redirects via the Client call
chain ("$client = new Client();" and "->setAllowRedirects(false)->fetch($url)"),
which will break legitimate redirected image URLs; change the configuration to
allow redirects (use the same pattern as the Favicon endpoint by passing an
allowance for up to 5 redirects, e.g. replace setAllowRedirects(false) with
setAllowRedirects(5) or an equivalent that enables up to 5 redirects) so remote
image fetching follows redirects before throwing
Exception::AVATAR_REMOTE_URL_FAILED.
In @src/Appwrite/Platform/Modules/Avatars/Http/Initials/Get.php:
- Around line 56-57: The param descriptions for the 'width' and 'height'
parameters are incorrect: they pass default value 500 but say "Defaults to 100."
Update the description strings for ->param('width', 500, new Range(0, 2000),
...) and ->param('height', 500, new Range(0, 2000), ...) to state "Defaults to
500." so the documentation matches the actual default values.
In @src/Appwrite/Platform/Modules/Avatars/Http/QR/Get.php:
- Around line 80-83: The QR avatar endpoint currently uses $response->send(...)
which differs from other avatar endpoints; replace the use of ->send() with
->file() so the response uses $response->file($image->output('png', 90)) and
keep the existing headers (Cache-Control and setContentType) unchanged to match
the pattern used by Initials/Image/Favicon/Screenshots/Cards endpoints.
In @src/Appwrite/Platform/Modules/Avatars/Http/Screenshots/Get.php:
- Around line 221-223: The catch block in Get.php currently swallows the
original exception by always throwing new
Exception(Exception::AVATAR_REMOTE_URL_FAILED, 'Screenshot generation failed: '
. $th->getMessage()), so preserve the original exception context: if $th is
already an Exception with a specific code (e.g.,
Exception::AVATAR_IMAGE_NOT_FOUND) rethrow $th; otherwise throw the
AVATAR_REMOTE_URL_FAILED but pass $th as the previous exception (third
constructor arg) so the original stack/type is retained; update the catch in the
method that contains this try/catch to implement this logic.
- Line 75: Update the timezone example to use the correct IANA casing: in the
param definition for 'timezone' (the ->param('timezone', '', new
WhiteList(timezone_identifiers_list()), ...) call in Get.php) replace the
example value 'america/new_york' with 'America/New_York' so it matches the
identifiers returned by timezone_identifiers_list() and the WhiteList validator.
🧹 Nitpick comments (14)
src/Appwrite/Platform/Modules/Avatars/Services/Http.php (1)
7-7: Consider more explicit naming for cloud card variants.The import alias
GetCloudCardforCards\Cloud\Front\Getis implicit about being the "Front" variant, whileGetCloudCardBackandGetCloudCardOGare explicit. Consider renaming toGetCloudCardFrontfor consistency.♻️ Proposed naming improvement
-use Appwrite\Platform\Modules\Avatars\Http\Cards\Cloud\Front\Get as GetCloudCard; +use Appwrite\Platform\Modules\Avatars\Http\Cards\Cloud\Front\Get as GetCloudCardFront;- $this->addAction(GetCloudCard::getName(), new GetCloudCard()); + $this->addAction(GetCloudCardFront::getName(), new GetCloudCardFront());Also applies to: 32-32
src/Appwrite/Platform/Modules/Avatars/Http/Initials/Get.php (2)
33-42: Missingcachelabel.Other avatar endpoints (e.g., CreditCards, Browsers, Favicon) include
->label('cache', true). This endpoint only hascache.resourcebut is missing the primarycachelabel, which may cause inconsistent caching behavior.📝 Proposed fix
->groups(['api', 'avatars']) ->label('scope', 'avatars.read') + ->label('cache', true) ->label('cache.resource', 'avatar/initials')
99-125: Consider explicitly destroying Imagick objects.The
$image,$punch, and$drawImagick objects are not explicitly destroyed. While PHP's garbage collector will handle this, explicit cleanup viadestroy()is recommended for memory-intensive image operations.♻️ Proposed fix
+ $imageBlob = $image->getImageBlob(); + + $draw->clear(); + $punch->destroy(); + $image->destroy(); + $response ->addHeader('Cache-Control', 'private, max-age=3888000') // 45 days ->setContentType('image/png') - ->file($image->getImageBlob()); + ->file($imageBlob); }src/Appwrite/Platform/Modules/Avatars/Http/Favicon/Get.php (1)
176-188: ICO validation heuristic is fragile.The check
stripos($data, '<html') === 0 || stripos($data, '<!doc') === 0only detects HTML at the very start of the response body. A valid ICO file could theoretically start with bytes that match these patterns, or an error page might have leading whitespace.Consider checking the response
Content-Typeheader as a primary indicator before falling back to content inspection:+ $contentType = $res->getHeader('Content-Type')[0] ?? ''; + $isHtmlResponse = \str_contains($contentType, 'text/html'); + if ('ico' === $outputExt) { if ( empty($data) || + $isHtmlResponse || stripos($data, '<html') === 0 || stripos($data, '<!doc') === 0 ) {src/Appwrite/Platform/Modules/Avatars/Http/Cards/Cloud/Back/Get.php (1)
47-49: Injected$userparameter is immediately overwritten.The
$useris injected via->inject('user')but the action immediately overwrites it with a fresh database query. Either remove the injection or utilize the injected user when$userIdmatches.♻️ Proposed fix - remove unused injection
->param('height', 0, new Range(0, 320), 'Resize image height, Pass an integer between 0 to 320.', true) - ->inject('user') ->inject('project') ->inject('dbForProject')And update the action signature:
- public function action(string $userId, string $mock, int $width, int $height, Document $user, Document $project, Database $dbForProject, Database $dbForPlatform, Response $response, array $heroes, array $contributors, array $employees, ?Logger $logger) + public function action(string $userId, string $mock, int $width, int $height, Document $project, Database $dbForProject, Database $dbForPlatform, Response $response, array $heroes, array $contributors, array $employees, ?Logger $logger)Also applies to: 59-61
src/Appwrite/Platform/Modules/Avatars/Http/Image/Get.php (1)
64-66: Remove redundant variable assignments.The
$outputand$typevariables are both always'png', and the conditional(empty($output)) ? $type : $outputon line 98 will never trigger. This is dead code.♻️ Proposed fix
public function action(string $url, int $width, int $height, Response $response) { $quality = 80; - $output = 'png'; - $type = 'png'; // ... existing code ... $image->crop((int) $width, (int) $height); - $output = (empty($output)) ? $type : $output; - $data = $image->output($output, $quality); + $data = $image->output('png', $quality);Also applies to: 97-98
src/Appwrite/Platform/Modules/Avatars/Http/QR/Get.php (1)
36-52: Missingcacheandcache.resourcelabels.Unlike other avatar endpoints (Browsers, CreditCards, Favicon, Image), this endpoint doesn't have the
cacheandcache.resourcelabels, which may lead to inconsistent caching behavior for QR codes.📝 Proposed fix
->groups(['api', 'avatars']) ->label('scope', 'avatars.read') + ->label('cache', true) + ->label('cache.resource', 'avatar/qr') ->label('sdk', new Method(src/Appwrite/Platform/Modules/Avatars/Http/Screenshots/Get.php (1)
154-161: Geolocation at (0, 0) is a valid coordinate but will be ignored.The condition
$latitude != 0 || $longitude != 0excludes the valid coordinate (0, 0) in the Gulf of Guinea. While this is an edge case, consider using a separate flag ornulldefaults to distinguish "not provided" from "explicitly set to zero."src/Appwrite/Platform/Modules/Avatars/Http/Action.php (2)
61-61: Unused$loggerparameter.The
$loggerparameter is declared but never used ingetUserGitHub. Either remove it or add logging for OAuth token refresh failures and race condition retries to aid debugging.
42-53: Dead code:$outputreassignment is a no-op.
$outputis set to'png'on line 42, so the conditionempty($output)on line 52 is always false. The$typevariable (line 44) is also unused after being set.Proposed cleanup
- $output = 'png'; $path = $set[$code]['path']; - $type = 'png'; if (!\is_readable($path)) { throw new Exception(Exception::GENERAL_SERVER_ERROR, 'File not readable in ' . $path); } $image = new Image(\file_get_contents($path)); $image->crop((int) $width, (int) $height); - $output = (empty($output)) ? $type : $output; - $data = $image->output($output, $quality); + $data = $image->output('png', $quality);src/Appwrite/Platform/Modules/Avatars/Http/Cards/Cloud/Front/Get.php (1)
59-61: Injected$userparameter is immediately overwritten.The
$userDocument is injected via->inject('user')at line 47 but immediately replaced at line 61 with a fresh database fetch using$userId. Either remove the injection or use the injected user when$userIdis empty.src/Appwrite/Platform/Modules/Avatars/Http/Cards/Cloud/OG/Get.php (3)
59-61: Injected$userparameter is immediately overwritten.Same issue as in
Front/Get.php: the$userDocument is injected but replaced at line 61 with a fresh database fetch.
67-100: Significant code duplication withFront/Get.php.The role determination logic (isHero, isContributor, isEmployee, isPlatinum), GitHub fetching, memberSince calculation, and mutual exclusion normalization (lines 123-140) are nearly identical to
Front/Get.php. Consider extracting this common logic to a shared method in the baseActionclass to improve maintainability.Also applies to: 123-140
284-287: Commented-outiconvlines should be removed or explained.The commented-out transliteration code appears in both this file and
Front/Get.php. If it's no longer needed, remove it; if it's being preserved for future use, add a TODO comment explaining why.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (16)
app/config/services.phpsrc/Appwrite/Platform/Appwrite.phpsrc/Appwrite/Platform/Modules/Avatars/Http/Action.phpsrc/Appwrite/Platform/Modules/Avatars/Http/Browsers/Get.phpsrc/Appwrite/Platform/Modules/Avatars/Http/Cards/Cloud/Back/Get.phpsrc/Appwrite/Platform/Modules/Avatars/Http/Cards/Cloud/Front/Get.phpsrc/Appwrite/Platform/Modules/Avatars/Http/Cards/Cloud/OG/Get.phpsrc/Appwrite/Platform/Modules/Avatars/Http/CreditCards/Get.phpsrc/Appwrite/Platform/Modules/Avatars/Http/Favicon/Get.phpsrc/Appwrite/Platform/Modules/Avatars/Http/Flags/Get.phpsrc/Appwrite/Platform/Modules/Avatars/Http/Image/Get.phpsrc/Appwrite/Platform/Modules/Avatars/Http/Initials/Get.phpsrc/Appwrite/Platform/Modules/Avatars/Http/QR/Get.phpsrc/Appwrite/Platform/Modules/Avatars/Http/Screenshots/Get.phpsrc/Appwrite/Platform/Modules/Avatars/Module.phpsrc/Appwrite/Platform/Modules/Avatars/Services/Http.php
🧰 Additional context used
🧬 Code graph analysis (13)
src/Appwrite/Platform/Modules/Avatars/Http/Flags/Get.php (1)
src/Appwrite/Platform/Modules/Avatars/Http/Action.php (2)
Action(17-158)avatarCallback(24-59)
src/Appwrite/Platform/Modules/Avatars/Http/CreditCards/Get.php (1)
src/Appwrite/Platform/Modules/Avatars/Http/Action.php (2)
Action(17-158)avatarCallback(24-59)
src/Appwrite/Platform/Modules/Avatars/Module.php (2)
src/Appwrite/Platform/Appwrite.php (1)
Appwrite(18-34)src/Appwrite/Platform/Modules/Avatars/Services/Http.php (1)
Http(18-36)
src/Appwrite/Platform/Modules/Avatars/Http/Browsers/Get.php (1)
src/Appwrite/Platform/Modules/Avatars/Http/Action.php (2)
Action(17-158)avatarCallback(24-59)
src/Appwrite/Platform/Modules/Avatars/Http/QR/Get.php (2)
src/Appwrite/Platform/Modules/Avatars/Http/Action.php (1)
Action(17-158)src/Appwrite/Platform/Modules/Avatars/Http/Image/Get.php (4)
Get(21-107)getName(25-28)__construct(30-60)action(62-106)
src/Appwrite/Platform/Modules/Avatars/Http/Cards/Cloud/Back/Get.php (2)
src/Appwrite/Platform/Modules/Avatars/Http/Action.php (3)
Action(17-158)getUserGitHub(61-157)getAppRoot(19-22)src/Appwrite/Platform/Modules/Avatars/Http/Cards/Cloud/Front/Get.php (4)
Get(21-244)getName(25-28)__construct(30-57)action(59-243)
src/Appwrite/Platform/Modules/Avatars/Http/Initials/Get.php (1)
src/Appwrite/Platform/Modules/Avatars/Http/Action.php (2)
Action(17-158)getAppRoot(19-22)
src/Appwrite/Platform/Modules/Avatars/Http/Cards/Cloud/OG/Get.php (3)
src/Appwrite/Platform/Modules/Avatars/Http/Action.php (3)
Action(17-158)getUserGitHub(61-157)getAppRoot(19-22)src/Appwrite/Platform/Modules/Avatars/Http/Cards/Cloud/Back/Get.php (4)
Get(21-115)getName(25-28)__construct(30-57)action(59-114)src/Appwrite/Platform/Modules/Avatars/Http/Cards/Cloud/Front/Get.php (4)
Get(21-244)getName(25-28)__construct(30-57)action(59-243)
src/Appwrite/Platform/Modules/Avatars/Http/Image/Get.php (5)
src/Appwrite/Platform/Modules/Avatars/Http/Action.php (1)
Action(17-158)src/Appwrite/Platform/Modules/Avatars/Http/Initials/Get.php (4)
Get(22-127)getName(26-29)__construct(31-62)action(64-126)src/Appwrite/Platform/Modules/Avatars/Http/CreditCards/Get.php (4)
Get(18-64)getName(22-25)__construct(27-58)action(60-63)src/Appwrite/Platform/Modules/Avatars/Http/Favicon/Get.php (4)
Get(25-216)getName(29-32)__construct(34-62)action(64-215)src/Appwrite/Platform/Modules/Avatars/Http/QR/Get.php (4)
Get(21-85)getName(25-28)__construct(30-59)action(61-84)
src/Appwrite/Platform/Appwrite.php (2)
src/Appwrite/Platform/Modules/Avatars/Module.php (1)
Module(8-14)src/Appwrite/Platform/Modules/Account/Module.php (1)
Module(8-14)
src/Appwrite/Platform/Modules/Avatars/Http/Action.php (1)
src/Appwrite/Extend/Exception.php (1)
Exception(7-472)
src/Appwrite/Platform/Modules/Avatars/Http/Cards/Cloud/Front/Get.php (2)
src/Appwrite/Platform/Modules/Avatars/Http/Action.php (3)
Action(17-158)getUserGitHub(61-157)getAppRoot(19-22)src/Appwrite/Platform/Modules/Avatars/Http/Cards/Cloud/Back/Get.php (4)
Get(21-115)getName(25-28)__construct(30-57)action(59-114)
src/Appwrite/Platform/Modules/Avatars/Http/Screenshots/Get.php (2)
src/Appwrite/Platform/Modules/Avatars/Http/Action.php (1)
Action(17-158)src/Appwrite/Platform/Modules/Avatars/Http/Image/Get.php (4)
Get(21-107)getName(25-28)__construct(30-60)action(62-106)
🪛 PHPMD (2.15.0)
src/Appwrite/Platform/Modules/Avatars/Http/Action.php
61-61: Avoid unused parameters such as '$logger'. (undefined)
(UnusedFormalParameter)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: E2E Service Test (Databases/Legacy)
- GitHub Check: E2E Service Test (Databases/TablesDB)
🔇 Additional comments (20)
src/Appwrite/Platform/Appwrite.php (1)
6-6: LGTM! Module registration follows the established pattern.The Avatars module import and registration are consistent with other modules in the platform. The placement maintains alphabetical ordering in the constructor.
Also applies to: 24-24
app/config/services.php (1)
149-149: Documentation comment added for storage service.Good practice to document that the storage service uses module-based routing.
src/Appwrite/Platform/Modules/Avatars/Module.php (1)
8-13: LGTM! Module implementation is clean and correct.The Avatars module follows the exact same pattern as other modules (e.g., Account). The HTTP service registration is straightforward and properly structured.
src/Appwrite/Platform/Modules/Avatars/Http/Flags/Get.php (3)
27-58: LGTM! Flag endpoint implementation follows established patterns.The endpoint configuration is consistent with other avatar endpoints (e.g., browsers). The validation rules, caching labels, and SDK configuration are properly structured.
60-63: Action base class properly provides avatarCallback method.The Action base class exists at
src/Appwrite/Platform/Modules/Avatars/Http/Action.phpand correctly implements theavatarCallbackmethod with proper error handling for missing avatar sets, invalid codes, and required extensions.
52-52: No action required. Theavatar-flagsconfiguration is properly defined inapp/config/avatars/flags.php, loaded during app initialization inapp/init/configs.php, and contains the expected ISO Alpha-2 country code mappings with consistent structure (nameandpathproperties). The WhiteList validator will function correctly.src/Appwrite/Platform/Modules/Avatars/Services/Http.php (1)
20-35: All avatar action classes are implemented and properly configured. All 11 referenced classes exist at their expected paths and are correctly imported with appropriate aliases in the Http.php service file.src/Appwrite/Platform/Modules/Avatars/Http/CreditCards/Get.php (1)
18-64: LGTM!Clean implementation following the established avatar endpoint pattern. The action correctly delegates to
avatarCallbackwith appropriate parameters.src/Appwrite/Platform/Modules/Avatars/Http/Browsers/Get.php (1)
18-64: LGTM!Clean implementation that correctly delegates to
avatarCallbackfollowing the established pattern for static avatar assets.src/Appwrite/Platform/Modules/Avatars/Http/QR/Get.php (1)
61-63: Do not remove the type coercion for$download.The manual coercion on line 63 is necessary, not redundant. The Utopia framework validates the Boolean parameter but does not type-cast it to a native
bool. Query string parameters likedownload=0remain as the string'0'after validation, which is truthy in PHP (non-empty string). Without the manual coercion,if ($download)would evaluate totrueeven when the user passesdownload=0.The same coercion pattern appears in the legacy code at
app/controllers/api/avatars.php:527, confirming this is the intended design to handle loose boolean values from query parameters correctly.Likely an incorrect or invalid review comment.
src/Appwrite/Platform/Modules/Avatars/Http/Screenshots/Get.php (3)
91-95: LGTM on the Imagick availability check.The extension check is appropriately placed at the start of the action, consistent with other avatar endpoints in the module.
103-104: Timeout configuration is appropriate.30-second timeout for browser service calls is reasonable for screenshot generation.
191-207: Image processing logic is well-structured.The conditional processing based on width/height/quality/output parameters avoids unnecessary image manipulation when defaults are used.
src/Appwrite/Platform/Modules/Avatars/Http/Action.php (2)
154-156: Exception handling only catchesAppwrite\Extend\Exception.The catch block on line 154 only catches the Appwrite-specific
Exceptionclass. Other exceptions (e.g., from OAuth provider, database operations) will propagate uncaught, potentially exposing implementation details to callers. Consider catching\Throwableif the intent is to gracefully degrade to an empty array for all failures.
118-143: Race condition retry loop is well-designed.The retry mechanism with 500ms sleep intervals (up to 10 retries = 5 seconds max) to handle concurrent token refresh is a reasonable approach. The break condition checking for token change is correct.
src/Appwrite/Platform/Modules/Avatars/Http/Cards/Cloud/Front/Get.php (3)
81-93:DateTimeconstructor with empty string uses current time.When
$heroes[$email]['memberSince']or$employees[$email]['memberSince']is missing or empty,new \DateTime('')defaults to the current time rather than throwing. This may be intentional but could lead to confusing "member since" dates if data is incomplete.
113-126: Role mutual exclusion logic is correct but verbose.The cascading
ifblocks ensure only one role is active. The logic works correctly: employee takes precedence, then hero, then contributor. Consider simplifying if readability is a concern.
235-237: Resize only when width OR height is non-zero may distort image.When only
$widthor only$heightis provided (but not both),resizeImage()with the other dimension as 0 may produce unexpected results depending on Imagick's behavior. Verify that this produces the intended scaling.src/Appwrite/Platform/Modules/Avatars/Http/Cards/Cloud/OG/Get.php (2)
110-118:employeeNumbermatch may not cover all employee mock variants.The match statement handles specific employee mocks but uses
default => ''for others. If a mock value like'employee-bg2'were added to the whitelist, it would matchstr_starts_with($mock, 'employee')at line 109 but get an empty employee number. This may be intentional but could cause subtle bugs if the whitelist grows.
162-246: Employee badge rendering logic is thorough.The per-variation positioning, rotation, and skew transformations for employee badges are well-implemented with appropriate handling of single vs. double-digit employee numbers.
| if (empty($verificationId)) { | ||
| throw new \Exception("Locked tokens."); // Race codition, handeled in catch | ||
| } |
There was a problem hiding this comment.
Typos in comment.
"Race codition, handeled in catch" should be "Race condition, handled in catch".
Proposed fix
- if (empty($verificationId)) {
- throw new \Exception("Locked tokens."); // Race codition, handeled in catch
- }
+ if (empty($verificationId)) {
+ throw new \Exception("Locked tokens."); // Race condition, handled in catch
+ }🤖 Prompt for AI Agents
In @src/Appwrite/Platform/Modules/Avatars/Http/Action.php around lines 106 -
108, Fix the typo in the inline comment next to the check for $verificationId in
Action.php: change "Race codition, handeled in catch" to "Race condition,
handled in catch" near the if (empty($verificationId)) { throw new
\Exception("Locked tokens."); } block so the comment reads correctly and
references the catch handling for the race condition.
| ->label('origin', '*') | ||
| ->param('userId', '', new UID(), 'User ID.', true) | ||
| ->param('mock', '', new WhiteList(['golden', 'normal', 'platinum']), 'Mocking behaviour.', true) | ||
| ->param('width', 0, new Range(0, 512), 'Resize image width, Pass an integer between 0 to 512.', true) |
There was a problem hiding this comment.
Minor typo: double space in description.
"Resize image width" has an extra space.
📝 Proposed fix
- ->param('width', 0, new Range(0, 512), 'Resize image width, Pass an integer between 0 to 512.', true)
+ ->param('width', 0, new Range(0, 512), 'Resize image width. Pass an integer between 0 to 512.', true)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| ->param('width', 0, new Range(0, 512), 'Resize image width, Pass an integer between 0 to 512.', true) | |
| ->param('width', 0, new Range(0, 512), 'Resize image width. Pass an integer between 0 to 512.', true) |
🤖 Prompt for AI Agents
In @src/Appwrite/Platform/Modules/Avatars/Http/Cards/Cloud/Back/Get.php at line
45, The parameter description for the 'width' param in the Get.php avatars card
has a double space ("Resize image width"); update the string passed to the
->param call for 'width' (the description argument in the builder/validator
invocation) to use a single space so it reads "Resize image width, Pass an
integer between 0 to 512.".
| setlocale(LC_ALL, "en_US.utf8"); | ||
| // $userId = \iconv("utf-8", "ascii//TRANSLIT", $userId); |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Remove dead code and unused setlocale call.
The setlocale call appears unnecessary, and the commented-out iconv line is dead code that should be removed.
🧹 Proposed fix
- setlocale(LC_ALL, "en_US.utf8");
- // $userId = \iconv("utf-8", "ascii//TRANSLIT", $userId);
-
$text = new ImagickDraw();📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| setlocale(LC_ALL, "en_US.utf8"); | |
| // $userId = \iconv("utf-8", "ascii//TRANSLIT", $userId); | |
| $text = new ImagickDraw(); |
🤖 Prompt for AI Agents
In @src/Appwrite/Platform/Modules/Avatars/Http/Cards/Cloud/Back/Get.php around
lines 95 - 96, Remove the unnecessary setlocale(LC_ALL, "en_US.utf8"); call and
delete the commented-out iconv line (// $userId = \iconv("utf-8",
"ascii//TRANSLIT", $userId);) in the Get.php cloud avatars handler so no dead
code or unintended locale side-effects remain; locate these statements near the
top of the Get class/method handling the cloud back card response and simply
remove both lines, leaving any real userId normalization logic intact if present
elsewhere.
| if (\strlen($name) > 32) { | ||
| $name = \substr($name, 0, 32); | ||
| } |
There was a problem hiding this comment.
UTF-8 string truncation may corrupt multibyte characters.
Using \strlen() and \substr() on UTF-8 strings can split multibyte characters. Consider using mb_strlen() and mb_substr() for safe Unicode handling.
Proposed fix
- if (\strlen($name) > 32) {
- $name = \substr($name, 0, 32);
+ if (\mb_strlen($name) > 32) {
+ $name = \mb_substr($name, 0, 32);
}
- if (\strlen($name) <= 23) {
+ if (\mb_strlen($name) <= 23) {
$text->setFontSize(80);
$scalingDown = false;Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In @src/Appwrite/Platform/Modules/Avatars/Http/Cards/Cloud/Front/Get.php around
lines 195 - 197, The truncation of $name in Get.php uses strlen() and substr(),
which can split multibyte UTF‑8 characters; replace the checks and trimming
around the $name variable (the block that currently uses \strlen($name) and
\substr($name, 0, 32)) with mb_strlen() and mb_substr() using the 'UTF-8'
encoding (or default internal encoding) so the string is measured and sliced by
characters not bytes; ensure mbstring is available or fall back safely if
needed.
| $client = new Client(); | ||
| try { | ||
| $res = $client | ||
| ->setAllowRedirects(false) | ||
| ->fetch($url); | ||
| } catch (\Throwable) { | ||
| throw new Exception(Exception::AVATAR_REMOTE_URL_FAILED); | ||
| } |
There was a problem hiding this comment.
Consider allowing redirects for remote image fetching.
The client is configured with setAllowRedirects(false), but many image hosting services and CDNs use redirects. This could cause legitimate image URLs to fail. The Favicon endpoint allows up to 5 redirects for similar remote fetching.
📝 Proposed fix
$client = new Client();
try {
$res = $client
- ->setAllowRedirects(false)
+ ->setAllowRedirects(true)
+ ->setMaxRedirects(5)
->fetch($url);
} catch (\Throwable) {
throw new Exception(Exception::AVATAR_REMOTE_URL_FAILED);
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| $client = new Client(); | |
| try { | |
| $res = $client | |
| ->setAllowRedirects(false) | |
| ->fetch($url); | |
| } catch (\Throwable) { | |
| throw new Exception(Exception::AVATAR_REMOTE_URL_FAILED); | |
| } | |
| $client = new Client(); | |
| try { | |
| $res = $client | |
| ->setAllowRedirects(true) | |
| ->setMaxRedirects(5) | |
| ->fetch($url); | |
| } catch (\Throwable) { | |
| throw new Exception(Exception::AVATAR_REMOTE_URL_FAILED); | |
| } |
🤖 Prompt for AI Agents
In @src/Appwrite/Platform/Modules/Avatars/Http/Image/Get.php around lines 78 -
85, The fetch currently disables redirects via the Client call chain ("$client =
new Client();" and "->setAllowRedirects(false)->fetch($url)"), which will break
legitimate redirected image URLs; change the configuration to allow redirects
(use the same pattern as the Favicon endpoint by passing an allowance for up to
5 redirects, e.g. replace setAllowRedirects(false) with setAllowRedirects(5) or
an equivalent that enables up to 5 redirects) so remote image fetching follows
redirects before throwing Exception::AVATAR_REMOTE_URL_FAILED.
| ->param('width', 500, new Range(0, 2000), 'Image width. Pass an integer between 0 to 2000. Defaults to 100.', true) | ||
| ->param('height', 500, new Range(0, 2000), 'Image height. Pass an integer between 0 to 2000. Defaults to 100.', true) |
There was a problem hiding this comment.
Documentation mismatch: default values don't match descriptions.
The width and height parameters default to 500, but the descriptions state "Defaults to 100." Update the descriptions to reflect the actual defaults.
📝 Proposed fix
- ->param('width', 500, new Range(0, 2000), 'Image width. Pass an integer between 0 to 2000. Defaults to 100.', true)
- ->param('height', 500, new Range(0, 2000), 'Image height. Pass an integer between 0 to 2000. Defaults to 100.', true)
+ ->param('width', 500, new Range(0, 2000), 'Image width. Pass an integer between 0 to 2000. Defaults to 500.', true)
+ ->param('height', 500, new Range(0, 2000), 'Image height. Pass an integer between 0 to 2000. Defaults to 500.', true)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| ->param('width', 500, new Range(0, 2000), 'Image width. Pass an integer between 0 to 2000. Defaults to 100.', true) | |
| ->param('height', 500, new Range(0, 2000), 'Image height. Pass an integer between 0 to 2000. Defaults to 100.', true) | |
| ->param('width', 500, new Range(0, 2000), 'Image width. Pass an integer between 0 to 2000. Defaults to 500.', true) | |
| ->param('height', 500, new Range(0, 2000), 'Image height. Pass an integer between 0 to 2000. Defaults to 500.', true) |
🤖 Prompt for AI Agents
In @src/Appwrite/Platform/Modules/Avatars/Http/Initials/Get.php around lines 56
- 57, The param descriptions for the 'width' and 'height' parameters are
incorrect: they pass default value 500 but say "Defaults to 100." Update the
description strings for ->param('width', 500, new Range(0, 2000), ...) and
->param('height', 500, new Range(0, 2000), ...) to state "Defaults to 500." so
the documentation matches the actual default values.
| $response | ||
| ->addHeader('Cache-Control', 'private, max-age=3888000') // 45 days | ||
| ->setContentType('image/png') | ||
| ->send($image->output('png', 90)); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check how other avatar endpoints output their responses
rg -n '->file\(|->send\(' src/Appwrite/Platform/Modules/Avatars/Http/ --type=phpRepository: appwrite/appwrite
Length of output: 465
🏁 Script executed:
#!/bin/bash
# Check how other avatar endpoints output their responses
rg '\->file\(|\->send\(' src/Appwrite/Platform/Modules/Avatars/Http/ --type=php -nRepository: appwrite/appwrite
Length of output: 1196
🏁 Script executed:
#!/bin/bash
# Get the QR endpoint code around the response
sed -n '75,85p' src/Appwrite/Platform/Modules/Avatars/Http/QR/Get.phpRepository: appwrite/appwrite
Length of output: 367
🏁 Script executed:
#!/bin/bash
# Check the Initials endpoint
sed -n '120,130p' src/Appwrite/Platform/Modules/Avatars/Http/Initials/Get.php
echo "---"
# Check the Image endpoint
sed -n '99,109p' src/Appwrite/Platform/Modules/Avatars/Http/Image/Get.phpRepository: appwrite/appwrite
Length of output: 585
Use file() instead of send() for consistency with all other avatar endpoints.
The QR endpoint is the only avatar endpoint using ->send() to output image data; all other endpoints (Initials, Image, Favicon, Screenshots, Cards) use ->file(). Notably, the Image endpoint uses ->file($image->output(...)) with the exact same data type. Change line 83 to use ->file($image->output('png', 90)) to match the standard pattern.
🤖 Prompt for AI Agents
In @src/Appwrite/Platform/Modules/Avatars/Http/QR/Get.php around lines 80 - 83,
The QR avatar endpoint currently uses $response->send(...) which differs from
other avatar endpoints; replace the use of ->send() with ->file() so the
response uses $response->file($image->output('png', 90)) and keep the existing
headers (Cache-Control and setContentType) unchanged to match the pattern used
by Initials/Image/Favicon/Screenshots/Cards endpoints.
| ->param('userAgent', '', new Text(512), 'Custom user agent string. Defaults to browser default.', true, example: 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X) AppleWebKit/605.1.15') | ||
| ->param('fullpage', false, new Boolean(true), 'Capture full page scroll. Pass 0 for viewport only, or 1 for full page. Defaults to 0.', true, example: 'true') | ||
| ->param('locale', '', new Text(10), 'Browser locale (e.g., "en-US", "fr-FR"). Defaults to browser default.', true, example: 'en-US') | ||
| ->param('timezone', '', new WhiteList(timezone_identifiers_list()), 'IANA timezone identifier (e.g., "America/New_York", "Europe/London"). Defaults to browser default.', true, example: 'america/new_york') |
There was a problem hiding this comment.
Timezone example uses incorrect casing.
The example 'america/new_york' should be 'America/New_York' to match the IANA timezone identifier format. The WhiteList validator uses timezone_identifiers_list() which returns properly cased identifiers.
Proposed fix
- ->param('timezone', '', new WhiteList(timezone_identifiers_list()), 'IANA timezone identifier (e.g., "America/New_York", "Europe/London"). Defaults to browser default.', true, example: 'america/new_york')
+ ->param('timezone', '', new WhiteList(timezone_identifiers_list()), 'IANA timezone identifier (e.g., "America/New_York", "Europe/London"). Defaults to browser default.', true, example: 'America/New_York')📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| ->param('timezone', '', new WhiteList(timezone_identifiers_list()), 'IANA timezone identifier (e.g., "America/New_York", "Europe/London"). Defaults to browser default.', true, example: 'america/new_york') | |
| ->param('timezone', '', new WhiteList(timezone_identifiers_list()), 'IANA timezone identifier (e.g., "America/New_York", "Europe/London"). Defaults to browser default.', true, example: 'America/New_York') |
🤖 Prompt for AI Agents
In @src/Appwrite/Platform/Modules/Avatars/Http/Screenshots/Get.php at line 75,
Update the timezone example to use the correct IANA casing: in the param
definition for 'timezone' (the ->param('timezone', '', new
WhiteList(timezone_identifiers_list()), ...) call in Get.php) replace the
example value 'america/new_york' with 'America/New_York' so it matches the
identifiers returned by timezone_identifiers_list() and the WhiteList validator.
| } catch (\Throwable $th) { | ||
| throw new Exception(Exception::AVATAR_REMOTE_URL_FAILED, 'Screenshot generation failed: ' . $th->getMessage()); | ||
| } |
There was a problem hiding this comment.
Original exception context is swallowed.
All exceptions thrown within the try block are caught and re-thrown as AVATAR_REMOTE_URL_FAILED, losing the original exception type (including the more specific AVATAR_IMAGE_NOT_FOUND from line 188). Consider preserving specific exception types or at least passing the original exception as a previous exception.
Proposed fix
- } catch (\Throwable $th) {
- throw new Exception(Exception::AVATAR_REMOTE_URL_FAILED, 'Screenshot generation failed: ' . $th->getMessage());
+ } catch (Exception $e) {
+ throw $e; // Re-throw Appwrite exceptions as-is
+ } catch (\Throwable $th) {
+ throw new Exception(Exception::AVATAR_REMOTE_URL_FAILED, 'Screenshot generation failed: ' . $th->getMessage());
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| } catch (\Throwable $th) { | |
| throw new Exception(Exception::AVATAR_REMOTE_URL_FAILED, 'Screenshot generation failed: ' . $th->getMessage()); | |
| } | |
| } catch (Exception $e) { | |
| throw $e; // Re-throw Appwrite exceptions as-is | |
| } catch (\Throwable $th) { | |
| throw new Exception(Exception::AVATAR_REMOTE_URL_FAILED, 'Screenshot generation failed: ' . $th->getMessage()); | |
| } |
🤖 Prompt for AI Agents
In @src/Appwrite/Platform/Modules/Avatars/Http/Screenshots/Get.php around lines
221 - 223, The catch block in Get.php currently swallows the original exception
by always throwing new Exception(Exception::AVATAR_REMOTE_URL_FAILED,
'Screenshot generation failed: ' . $th->getMessage()), so preserve the original
exception context: if $th is already an Exception with a specific code (e.g.,
Exception::AVATAR_IMAGE_NOT_FOUND) rethrow $th; otherwise throw the
AVATAR_REMOTE_URL_FAILED but pass $th as the previous exception (third
constructor arg) so the original stack/type is retained; update the catch in the
method that contains this try/catch to implement this logic.
abnegate
left a comment
There was a problem hiding this comment.
Looks good, let's make sure tests still pass with the old controller removed
What does this PR do?
(Provide a description of what this PR does and why it's needed.)
Test Plan
(Write your test plan here. If you changed any code, please provide us with clear instructions on how you verified your changes work. Screenshots may also be helpful.)
Related PRs and Issues
Checklist