Describe the bug
OCA\Photos\Controller\PreviewController::getFileIdForAlbums() crashes with Call to a member function getOwner() on null whenever a user has access to multiple albums and one of them does not contain or cannot resolve the requested file. The crash aborts the entire preview request, so every tile returns HTTP 500 even though the underlying files and album data are fine. This also breaks nextcloud/memories, which uses the Photos preview endpoint as a tile source.
The offending lines in apps/photos/lib/Controller/PreviewController.php (around line 130-135):
if ($albumFile === null) {
$albumFiles = $this->filtersManager->getFilesBasedOnFilters($album->getUserId(), $album->getDecodedFilters(), $fileId);
$albumFile = array_pop($albumFiles); // stays null if $albumFiles is empty
}
$nodes = $this->rootFolder
->getUserFolder($albumFile->getOwner()) // crash here
->getById($fileId);
There is no null guard between the array_pop() and the dereference. As soon as the iteration hits an album where the file is neither directly mapped nor reachable via filters, the whole request dies.
To Reproduce
- As owner: create several albums, add photos, share at least one album with another user.
- Log in as the recipient.
- Open Memories (or any UI that fetches
/apps/photos/api/v1/preview/<fileId>?x=512&y=512).
- All preview tiles return HTTP 500. Server log shows
Call to a member function getOwner() on null at PreviewController.php:135.
In my setup the recipient sees 11 albums. A DB audit confirmed file_count == resolvable_count for every album (no orphan rows in oc_photos_albums_files, no missing oc_filecache entries). The data is clean, the bug is purely in the iteration.
Expected behavior
If a given album does not yield a resolvable file for the requested $fileId, skip it and continue iterating, exactly as the surrounding if (\count($nodes) === 0) fallback already anticipates.
Screenshots
Not applicable. The grid renders empty grey tiles. Server returns HTTP 500 directly when the preview URL is opened in a new tab.
Desktop:
- OS: macOS 15 (Sequoia), also tested on Windows 11
- Browser: Safari 18.0.1, Firefox 150, Chrome/Edge 147
- The bug is server-side and reproduces in every browser.
Smartphone: not tested, but irrelevant since the failure is in PHP.
Browser log
Browser console shows nothing useful because the response is intercepted by the Photos service worker. The Network tab shows the request as failed with no headers. The server-side log is what matters:
{"level":2,"app":"PHP","message":"Undefined array key 0 at /var/www/html/apps/photos/lib/Album/AlbumMapper.php#209"}
{"level":3,"app":"index","message":"Call to a member function getOwner() on null in file '/var/www/html/apps/photos/lib/Controller/PreviewController.php' line 135",
"exception":{
"Message":"Call to a member function getOwner() on null",
"File":"/var/www/html/apps/photos/lib/Controller/PreviewController.php",
"Line":135,
"Trace":[
"PreviewController->getFileIdForAlbums(<fileId>, [11 × AlbumInfo])",
"PreviewController->index(<fileId>, 512, 512)",
"Dispatcher->executeController(...)",
"Dispatcher->dispatch(...)",
"Router->main(...)"
]
}
}
Additional context
- Nextcloud: 32.0.1.2
- Photos app: 5.0.0-dev.1
- Setup: Docker, behind Traefik and Cloudflare. Reverse proxy is not involved (server returns 500 itself).
Suggested fix (verified locally, restores previews and album sections):
protected function getFileIdForAlbums(int $fileId, array $albums): array {
foreach ($albums as $album) {
$albumFile = $this->albumMapper->getForAlbumIdAndFileId($album->getId(), $fileId);
if ($albumFile === null) {
$albumFiles = $this->filtersManager->getFilesBasedOnFilters($album->getUserId(), $album->getDecodedFilters(), $fileId);
$albumFile = array_pop($albumFiles);
+ if ($albumFile === null) {
+ continue;
+ }
}
$nodes = $this->rootFolder
->getUserFolder($albumFile->getOwner())
->getById($fileId);
if (\count($nodes) !== 0) {
return $nodes;
}
}
return [];
}
Describe the bug
OCA\Photos\Controller\PreviewController::getFileIdForAlbums()crashes withCall to a member function getOwner() on nullwhenever a user has access to multiple albums and one of them does not contain or cannot resolve the requested file. The crash aborts the entire preview request, so every tile returns HTTP 500 even though the underlying files and album data are fine. This also breaksnextcloud/memories, which uses the Photos preview endpoint as a tile source.The offending lines in
apps/photos/lib/Controller/PreviewController.php(around line 130-135):There is no null guard between the
array_pop()and the dereference. As soon as the iteration hits an album where the file is neither directly mapped nor reachable via filters, the whole request dies.To Reproduce
/apps/photos/api/v1/preview/<fileId>?x=512&y=512).Call to a member function getOwner() on nullatPreviewController.php:135.In my setup the recipient sees 11 albums. A DB audit confirmed
file_count == resolvable_countfor every album (no orphan rows inoc_photos_albums_files, no missingoc_filecacheentries). The data is clean, the bug is purely in the iteration.Expected behavior
If a given album does not yield a resolvable file for the requested
$fileId, skip it and continue iterating, exactly as the surroundingif (\count($nodes) === 0)fallback already anticipates.Screenshots
Not applicable. The grid renders empty grey tiles. Server returns HTTP 500 directly when the preview URL is opened in a new tab.
Desktop:
Smartphone: not tested, but irrelevant since the failure is in PHP.
Browser log
Browser console shows nothing useful because the response is intercepted by the Photos service worker. The Network tab shows the request as failed with no headers. The server-side log is what matters:
{"level":2,"app":"PHP","message":"Undefined array key 0 at /var/www/html/apps/photos/lib/Album/AlbumMapper.php#209"} {"level":3,"app":"index","message":"Call to a member function getOwner() on null in file '/var/www/html/apps/photos/lib/Controller/PreviewController.php' line 135", "exception":{ "Message":"Call to a member function getOwner() on null", "File":"/var/www/html/apps/photos/lib/Controller/PreviewController.php", "Line":135, "Trace":[ "PreviewController->getFileIdForAlbums(<fileId>, [11 × AlbumInfo])", "PreviewController->index(<fileId>, 512, 512)", "Dispatcher->executeController(...)", "Dispatcher->dispatch(...)", "Router->main(...)" ] } }Additional context
Suggested fix (verified locally, restores previews and album sections):
protected function getFileIdForAlbums(int $fileId, array $albums): array { foreach ($albums as $album) { $albumFile = $this->albumMapper->getForAlbumIdAndFileId($album->getId(), $fileId); if ($albumFile === null) { $albumFiles = $this->filtersManager->getFilesBasedOnFilters($album->getUserId(), $album->getDecodedFilters(), $fileId); $albumFile = array_pop($albumFiles); + if ($albumFile === null) { + continue; + } } $nodes = $this->rootFolder ->getUserFolder($albumFile->getOwner()) ->getById($fileId); if (\count($nodes) !== 0) { return $nodes; } } return []; }