diff --git a/lib/Controller/ApiController.php b/lib/Controller/ApiController.php index c8ef2437..420b49f5 100644 --- a/lib/Controller/ApiController.php +++ b/lib/Controller/ApiController.php @@ -95,6 +95,12 @@ class ApiController extends Controller return new JSONResponse(['message' => 'Folder not found'], Http::STATUS_NOT_FOUND); } + // Remove folder if album + // Permissions will be checked during the transform + if ($this->request->getParam('album')) { + $folder = null; + } + // Run actual query try { $list = $this->timelineQuery->getDays( @@ -255,7 +261,6 @@ class ApiController extends Controller /** * @NoAdminRequired - * @NoCSRFRequired * * Get list of albums with counts of images */ @@ -672,12 +677,18 @@ class ApiController extends Controller // Filter only for one tag if ($this->tagsIsEnabled()) { - $tagName = $this->request->getParam('tag'); - if ($tagName) { + if ($tagName = $this->request->getParam('tag')) { $transforms[] = [$this->timelineQuery, 'transformTagFilter', $tagName]; } } + // Filter for one album + if ($this->albumsIsEnabled()) { + if ($albumId = $this->request->getParam('album')) { + $transforms[] = [$this->timelineQuery, 'transformAlbumFilter', $albumId]; + } + } + // Limit number of responses for day query $limit = $this->request->getParam('limit'); if ($limit) { @@ -687,8 +698,15 @@ class ApiController extends Controller return $transforms; } - /** Preload a few "day" at the start of "days" response */ - private function preloadDays(array &$days, Folder &$folder, bool $recursive, bool $archive) + /** + * Preload a few "day" at the start of "days" response. + * + * @param array $days the days array + * @param null|Folder $folder the folder to search in + * @param bool $recursive search in subfolders + * @param bool $archive search in archive folder only + */ + private function preloadDays(array &$days, &$folder, bool $recursive, bool $archive) { $uid = $this->userSession->getUser()->getUID(); $transforms = $this->getTransformations(false); @@ -696,13 +714,15 @@ class ApiController extends Controller $preloadDayIds = []; $preloadDays = []; foreach ($days as &$day) { - if ($day['count'] <= 0) continue; + if ($day['count'] <= 0) { + continue; + } $preloaded += $day['count']; $preloadDayIds[] = $day['dayid']; $preloadDays[] = &$day; - if ($preloaded >= 50 || count($preloadDayIds) > 5) { // should be enough + if ($preloaded >= 50 || \count($preloadDayIds) > 5) { // should be enough break; } } diff --git a/lib/Db/TimelineQuery.php b/lib/Db/TimelineQuery.php index 8e8081dd..53d22095 100644 --- a/lib/Db/TimelineQuery.php +++ b/lib/Db/TimelineQuery.php @@ -8,11 +8,11 @@ use OCP\IDBConnection; class TimelineQuery { + use TimelineQueryAlbums; use TimelineQueryDays; use TimelineQueryFaces; use TimelineQueryFilters; use TimelineQueryTags; - use TimelineQueryAlbums; protected IDBConnection $connection; diff --git a/lib/Db/TimelineQueryAlbums.php b/lib/Db/TimelineQueryAlbums.php index 86df0636..b4b55164 100644 --- a/lib/Db/TimelineQueryAlbums.php +++ b/lib/Db/TimelineQueryAlbums.php @@ -4,12 +4,28 @@ declare(strict_types=1); namespace OCA\Memories\Db; +use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\IDBConnection; trait TimelineQueryAlbums { protected IDBConnection $connection; + /** Transform only for album */ + public function transformAlbumFilter(IQueryBuilder &$query, string $uid, string $albumId) + { + if (!$this->hasAlbumPermission($query->getConnection(), $uid, (int) $albumId)) { + throw new \Exception("Album {$albumId} not found"); + } + + // WHERE these are items with this album + $query->innerJoin('m', 'photos_albums_files', 'paf', $query->expr()->andX( + $query->expr()->eq('paf.album_id', $query->createNamedParameter($albumId)), + $query->expr()->eq('paf.file_id', 'm.fileid'), + )); + } + + /** Get list of albums */ public function getAlbums(string $uid) { $query = $this->connection->getQueryBuilder(); @@ -20,7 +36,7 @@ trait TimelineQueryAlbums $query->expr()->eq('user', $query->createNamedParameter($uid)), ); - // WHERE there are items with this tag + // WHERE these are items with this album $query->innerJoin('pa', 'photos_albums_files', 'paf', $query->expr()->andX( $query->expr()->eq('paf.album_id', 'pa.album_id'), )); @@ -29,7 +45,7 @@ trait TimelineQueryAlbums $query->innerJoin('paf', 'memories', 'm', $query->expr()->eq('m.fileid', 'paf.file_id')); // WHERE these photos are in the filecache - $query->innerJoin('m', 'filecache', 'f', $query->expr()->eq('m.fileid', 'f.fileid'),); + $query->innerJoin('m', 'filecache', 'f', $query->expr()->eq('m.fileid', 'f.fileid')); // GROUP and ORDER by $query->groupBy('pa.album_id'); @@ -48,4 +64,30 @@ trait TimelineQueryAlbums return $albums; } + + private function hasAlbumPermission(IDBConnection $conn, string $uid, int $albumId) + { + // Check if owner + $query = $conn->getQueryBuilder(); + $query->select('album_id')->from('photos_albums')->where( + $query->expr()->andX( + $query->expr()->eq('album_id', $query->createNamedParameter($albumId, IQueryBuilder::PARAM_INT)), + $query->expr()->eq('user', $query->createNamedParameter($uid)), + ) + ); + if (false !== $query->executeQuery()->fetchOne()) { + return true; + } + + // Check in collaborators + $query = $conn->getQueryBuilder(); + $query->select('album_id')->from('photos_collaborators')->where( + $query->expr()->andX( + $query->expr()->eq('album_id', $query->createNamedParameter($albumId, IQueryBuilder::PARAM_INT)), + $query->expr()->eq('collaborator_id', $query->createNamedParameter($uid)), + ) + ); + + return false !== $query->executeQuery()->fetchOne(); + } } diff --git a/lib/Db/TimelineQueryDays.php b/lib/Db/TimelineQueryDays.php index 711f465e..ce04ac33 100644 --- a/lib/Db/TimelineQueryDays.php +++ b/lib/Db/TimelineQueryDays.php @@ -15,15 +15,15 @@ trait TimelineQueryDays /** * Get the days response from the database for the timeline. * - * @param Folder $folder The folder to get the days from - * @param bool $recursive Whether to get the days recursively - * @param bool $archive Whether to get the days only from the archive folder - * @param array $queryTransforms An array of query transforms to apply to the query + * @param null|Folder $folder The folder to get the days from + * @param bool $recursive Whether to get the days recursively + * @param bool $archive Whether to get the days only from the archive folder + * @param array $queryTransforms An array of query transforms to apply to the query * * @return array The days response */ public function getDays( - Folder &$folder, + &$folder, string $uid, bool $recursive, bool $archive, @@ -56,18 +56,18 @@ trait TimelineQueryDays /** * Get the day response from the database for the timeline. * - * @param Folder $folder The folder to get the day from - * @param string $uid The user id - * @param int[] $dayid The day id - * @param bool $recursive If the query should be recursive - * @param bool $archive If the query should include only the archive folder - * @param array $queryTransforms The query transformations to apply - * @param mixed $day_ids + * @param null|Folder $folder The folder to get the day from + * @param string $uid The user id + * @param int[] $dayid The day id + * @param bool $recursive If the query should be recursive + * @param bool $archive If the query should include only the archive folder + * @param array $queryTransforms The query transformations to apply + * @param mixed $day_ids * * @return array An array of day responses */ public function getDay( - Folder &$folder, + &$folder, string $uid, $day_ids, bool $recursive, @@ -227,10 +227,10 @@ trait TimelineQueryDays /** * Get the query for oc_filecache join. * - * @param IQueryBuilder $query Query builder - * @param array|Folder $folder Either the top folder or array of folder Ids - * @param bool $recursive Whether to get the days recursively - * @param bool $archive Whether to get the days only from the archive folder + * @param IQueryBuilder $query Query builder + * @param null|array|Folder $folder Either the top folder or array of folder Ids + * @param bool $recursive Whether to get the days recursively + * @param bool $archive Whether to get the days only from the archive folder */ private function getFilecacheJoinQuery( IQueryBuilder &$query, @@ -238,7 +238,14 @@ trait TimelineQueryDays bool $recursive, bool $archive ) { - $pathQuery = null; + // Join with memories + $baseOp = $query->expr()->eq('f.fileid', 'm.fileid'); + if (null === $folder) { + return $baseOp; // No folder, get all + } + + // Filter by folder (recursive or otherwise) + $pathOp = null; if ($recursive) { // Get all subfolder Ids recursively $folderIds = []; @@ -250,15 +257,15 @@ trait TimelineQueryDays } // Join with folder IDs - $pathQuery = $query->expr()->in('f.parent', $query->createNamedParameter($folderIds, IQueryBuilder::PARAM_INT_ARRAY)); + $pathOp = $query->expr()->in('f.parent', $query->createNamedParameter($folderIds, IQueryBuilder::PARAM_INT_ARRAY)); } else { // If getting non-recursively folder only check for parent - $pathQuery = $query->expr()->eq('f.parent', $query->createNamedParameter($folder->getId(), IQueryBuilder::PARAM_INT)); + $pathOp = $query->expr()->eq('f.parent', $query->createNamedParameter($folder->getId(), IQueryBuilder::PARAM_INT)); } return $query->expr()->andX( - $query->expr()->eq('f.fileid', 'm.fileid'), - $pathQuery, + $baseOp, + $pathOp, ); } }