Skip to content

PreviewController::getFileIdForAlbums() crashes with "Call to a member function getOwner() on null" → all album previews return HTTP 500` #3501

Description

@jhchein

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

  1. As owner: create several albums, add photos, share at least one album with another user.
  2. Log in as the recipient.
  3. Open Memories (or any UI that fetches /apps/photos/api/v1/preview/<fileId>?x=512&y=512).
  4. 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 [];
 }

Metadata

Metadata

Assignees

No one assigned

    Labels

    0. Needs triagePending approval or rejection. This issue is pending approval.bugSomething isn't working

    Type

    No type

    Projects

    Status
    Backlog

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions