HEX
Server: LiteSpeed
System: Linux d8 4.18.0-553.121.1.lve.el8.x86_64 #1 SMP Thu Apr 30 16:40:41 UTC 2026 x86_64
User: wbwebdes (3015)
PHP: 8.1.31
Disabled: exec,system,passthru,shell_exec,proc_close,proc_open,dl,popen,show_source,posix_kill,posix_mkfifo,posix_getpwuid,posix_setpgid,posix_setsid,posix_setuid,posix_setgid,posix_seteuid,posix_setegid,posix_uname
Upload Files
File: /home/wbwebdes/domains/files.wb-cloud.nl/private_html/apps/photos/lib/Album/AlbumMapper.php
<?php

declare(strict_types=1);
/**
 * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors
 * SPDX-License-Identifier: AGPL-3.0-or-later
 */

namespace OCA\Photos\Album;

use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
use OCA\Photos\Exception\AlreadyInAlbumException;
use OCA\Photos\Filters\FiltersManager;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\Files\IMimeTypeLoader;
use OCP\IDBConnection;
use OCP\IGroupManager;
use OCP\IL10N;
use OCP\IUserManager;
use OCP\Security\ISecureRandom;

class AlbumMapper {

	// Same mapping as IShare.
	public const TYPE_USER = 0;
	public const TYPE_GROUP = 1;
	public const TYPE_LINK = 3;

	public function __construct(
		private readonly IDBConnection $connection,
		private readonly IMimeTypeLoader $mimeTypeLoader,
		private readonly ITimeFactory $timeFactory,
		private readonly IUserManager $userManager,
		private readonly IGroupManager $groupManager,
		private readonly IL10N $l,
		private readonly ISecureRandom $random,
		private readonly FiltersManager $filtersManager,
	) {
	}

	public function create(string $userId, string $name, string $location = '', ?string $filters = null): AlbumInfo {
		$created = $this->timeFactory->getTime();
		$query = $this->connection->getQueryBuilder();
		$query->insert('photos_albums')
			->values([
				'user' => $query->createNamedParameter($userId),
				'name' => $query->createNamedParameter($name),
				'location' => $query->createNamedParameter($location),
				'created' => $query->createNamedParameter($created, IQueryBuilder::PARAM_INT),
				'last_added_photo' => $query->createNamedParameter(-1, IQueryBuilder::PARAM_INT),
				'filters' => $query->createNamedParameter($filters, IQueryBuilder::PARAM_STR),
			]);
		$query->executeStatement();
		$id = $query->getLastInsertId();

		return new AlbumInfo($id, $userId, $name, $location, $created, -1, $filters);
	}

	public function get(int $id): ?AlbumInfo {
		$query = $this->connection->getQueryBuilder();
		$query->select('name', 'user', 'location', 'created', 'last_added_photo', 'filters')
			->from('photos_albums')
			->where($query->expr()->eq('album_id', $query->createNamedParameter($id, IQueryBuilder::PARAM_INT)));
		$row = $query->executeQuery()->fetch();
		if ($row) {
			return new AlbumInfo($id, $row['user'], $row['name'], $row['location'], (int)$row['created'], (int)$row['last_added_photo'], $row['filters']);
		} else {
			return null;
		}
	}

	/**
	 * @return AlbumInfo[]
	 */
	public function getForUser(string $userId): array {
		$query = $this->connection->getQueryBuilder();
		$query->select('album_id', 'name', 'location', 'created', 'last_added_photo', 'filters')
			->from('photos_albums')
			->where($query->expr()->eq('user', $query->createNamedParameter($userId)));
		$rows = $query->executeQuery()->fetchAll();
		return array_map(fn (array $row): AlbumInfo => new AlbumInfo((int)$row['album_id'], $userId, $row['name'], $row['location'], (int)$row['created'], (int)$row['last_added_photo'], $row['filters']), $rows);
	}

	/**
	 * @return AlbumInfo
	 */
	public function getByName(string $albumName, string $userName): ?AlbumInfo {
		$query = $this->connection->getQueryBuilder();
		$query->select('album_id', 'location', 'created', 'last_added_photo', 'filters')
			->from('photos_albums')
			->where($query->expr()->eq('name', $query->createNamedParameter($albumName)))
			->andWhere($query->expr()->eq('user', $query->createNamedParameter($userName)));
		$row = $query->executeQuery()->fetch();
		if ($row) {
			return new AlbumInfo((int)$row['album_id'], $userName, $albumName, $row['location'], (int)$row['created'], (int)$row['last_added_photo'], $row['filters']);
		} else {
			return null;
		}
	}

	/**
	 * @return AlbumInfo[]
	 */
	public function getForFile(int $fileId): array {
		$query = $this->connection->getQueryBuilder();
		$query->select('a.album_id', 'name', 'user', 'location', 'created', 'last_added_photo', 'filters')
			->from('photos_albums', 'a')
			->leftJoin('a', 'photos_albums_files', 'p', $query->expr()->eq('a.album_id', 'p.album_id'))
			->where($query->expr()->eq('file_id', $query->createNamedParameter($fileId, IQueryBuilder::PARAM_INT)));
		$rows = $query->executeQuery()->fetchAll();
		return array_map(fn (array $row): AlbumInfo => new AlbumInfo((int)$row['album_id'], $row['user'], $row['name'], $row['location'], (int)$row['created'], (int)$row['last_added_photo'], $row['filters']), $rows);
	}

	/**
	 * @return AlbumInfo[]
	 */
	public function getForUserAndFile(string $userId, int $fileId): array {
		$query = $this->connection->getQueryBuilder();
		$query->select('a.album_id', 'name', 'user', 'location', 'created', 'last_added_photo', 'filters')
			->from('photos_albums', 'a')
			->leftJoin('a', 'photos_albums_files', 'p', $query->expr()->eq('a.album_id', 'p.album_id'))
			->where($query->expr()->eq('user', $query->createNamedParameter($userId)))
			->andWhere($query->expr()->eq('file_id', $query->createNamedParameter($fileId, IQueryBuilder::PARAM_INT)));
		$rows = $query->executeQuery()->fetchAll();
		return array_map(fn (array $row): AlbumInfo => new AlbumInfo((int)$row['album_id'], $row['user'], $row['name'], $row['location'], (int)$row['created'], (int)$row['last_added_photo'], $row['filters']), $rows);
	}

	public function rename(int $id, string $newName): void {
		$query = $this->connection->getQueryBuilder();
		$query->update('photos_albums')
			->set('name', $query->createNamedParameter($newName))
			->where($query->expr()->eq('album_id', $query->createNamedParameter($id, IQueryBuilder::PARAM_INT)));
		$query->executeStatement();
	}

	public function setLocation(int $id, string $newLocation): void {
		$query = $this->connection->getQueryBuilder();
		$query->update('photos_albums')
			->set('location', $query->createNamedParameter($newLocation))
			->where($query->expr()->eq('album_id', $query->createNamedParameter($id, IQueryBuilder::PARAM_INT)));
		$query->executeStatement();
	}

	public function delete(int $id): void {
		$this->connection->beginTransaction();

		$query = $this->connection->getQueryBuilder();
		$query->delete('photos_albums')
			->where($query->expr()->eq('album_id', $query->createNamedParameter($id, IQueryBuilder::PARAM_INT)));
		$query->executeStatement();

		$query = $this->connection->getQueryBuilder();
		$query->delete('photos_albums_files')
			->where($query->expr()->eq('album_id', $query->createNamedParameter($id, IQueryBuilder::PARAM_INT)));
		$query->executeStatement();

		$query = $this->connection->getQueryBuilder();
		$query->delete('photos_albums_collabs')
			->where($query->expr()->eq('album_id', $query->createNamedParameter($id, IQueryBuilder::PARAM_INT)));
		$query->executeStatement();

		$this->connection->commit();
	}

	/**
	 * @return AlbumFile[]
	 */
	public function getForAlbumIdAndUserWithFiles(int $albumId, string $userId, array $filters): array {
		$query = $this->connection->getQueryBuilder();
		$query->select('fileid', 'mimetype', 'a.album_id', 'size', 'mtime', 'etag', 'added', 'owner')
			->selectAlias('f.name', 'file_name')
			->selectAlias('a.name', 'album_name')
			->from('photos_albums', 'a')
			->leftJoin('a', 'photos_albums_files', 'p', $query->expr()->eq('a.album_id', 'p.album_id'))
			->leftJoin('p', 'filecache', 'f', $query->expr()->eq('p.file_id', 'f.fileid'))
			->where($query->expr()->eq('a.album_id', $query->createNamedParameter($albumId, IQueryBuilder::PARAM_INT)))
			->andWhere($query->expr()->eq('user', $query->createNamedParameter($userId)));
		$rows = $query->executeQuery()->fetchAll();

		$files = [];
		foreach ($rows as $row) {
			if ($row['fileid']) {
				$mimeType = $this->mimeTypeLoader->getMimetypeById((int)$row['mimetype']);
				$files[] = new AlbumFile((int)$row['fileid'], $row['file_name'], $mimeType, (int)$row['size'], (int)$row['mtime'], $row['etag'], (int)$row['added'], $row['owner'], 'user');
			}
		}

		$fileIds = array_map(fn ($file) => $file->getFileId(), $files);

		$smartAlbumFiles = array_filter(
			$this->filtersManager->getFilesBasedOnFilters($userId, $filters),
			fn ($file) => array_search($file->getFileId(), $fileIds) === false,
		);

		return [...$files, ...$smartAlbumFiles];
	}

	public function getForAlbumIdAndFileId(int $albumId, int $fileId): ?AlbumFile {
		$query = $this->connection->getQueryBuilder();
		$query->select('fileid', 'mimetype', 'a.album_id', 'user', 'size', 'mtime', 'etag', 'location', 'created', 'last_added_photo', 'added', 'owner')
			->selectAlias('f.name', 'file_name')
			->selectAlias('a.name', 'album_name')
			->from('photos_albums', 'a')
			->leftJoin('a', 'photos_albums_files', 'p', $query->expr()->eq('a.album_id', 'p.album_id'))
			->leftJoin('p', 'filecache', 'f', $query->expr()->eq('p.file_id', 'f.fileid'))
			->where($query->expr()->eq('a.album_id', $query->createNamedParameter($albumId, IQueryBuilder::PARAM_INT)))
			->andWhere($query->expr()->eq('file_id', $query->createNamedParameter($fileId, IQueryBuilder::PARAM_INT)));
		$row = $query->executeQuery()->fetchAll()[0];

		if ($row === null) {
			return null;
		}

		$mimeType = $this->mimeTypeLoader->getMimetypeById((int)$row['mimetype']);
		return new AlbumFile((int)$row['fileid'], $row['file_name'], $mimeType, (int)$row['size'], (int)$row['mtime'], $row['etag'], (int)$row['added'], $row['owner'], 'user');
	}

	public function addFile(int $albumId, int $fileId, string $owner): void {
		$added = $this->timeFactory->getTime();
		try {
			$query = $this->connection->getQueryBuilder();
			$query->insert('photos_albums_files')
				->values([
					'album_id' => $query->createNamedParameter($albumId, IQueryBuilder::PARAM_INT),
					'file_id' => $query->createNamedParameter($fileId, IQueryBuilder::PARAM_INT),
					'added' => $query->createNamedParameter($added, IQueryBuilder::PARAM_INT),
					'owner' => $query->createNamedParameter($owner),
				]);
			$query->executeStatement();
		} catch (UniqueConstraintViolationException $e) {
			throw new AlreadyInAlbumException('File already in album', 0, $e);
		}

		$query = $this->connection->getQueryBuilder();
		$query->update('photos_albums')
			->set('last_added_photo', $query->createNamedParameter($fileId, IQueryBuilder::PARAM_INT))
			->where($query->expr()->eq('album_id', $query->createNamedParameter($albumId, IQueryBuilder::PARAM_INT)));
		$query->executeStatement();
	}

	public function removeFile(int $albumId, int $fileId): void {
		$query = $this->connection->getQueryBuilder();
		$query->delete('photos_albums_files')
			->where($query->expr()->eq('album_id', $query->createNamedParameter($albumId, IQueryBuilder::PARAM_INT)))
			->andWhere($query->expr()->eq('file_id', $query->createNamedParameter($fileId, IQueryBuilder::PARAM_INT)));
		$query->executeStatement();

		$query = $this->connection->getQueryBuilder();
		$query->update('photos_albums')
			->set('last_added_photo', $query->createNamedParameter($this->getLastAdded($albumId), IQueryBuilder::PARAM_INT))
			->where($query->expr()->eq('album_id', $query->createNamedParameter($albumId, IQueryBuilder::PARAM_INT)));
		$query->executeStatement();
	}

	/**
	 * Remove all files added by a user from an album.
	 */
	public function removeFilesForUser(int $albumId, string $userId) {
		// Remove all photos by this user from the album:
		$query = $this->connection->getQueryBuilder();
		$query->delete('photos_albums_files')
			->where($query->expr()->eq('album_id', $query->createNamedParameter($albumId, IQueryBuilder::PARAM_INT)))
			->andWhere($query->expr()->eq('owner', $query->createNamedParameter($userId)))
			->executeStatement();

		// Update the last added photo:
		$query = $this->connection->getQueryBuilder();
		$query->update('photos_albums')
			->set('last_added_photo', $query->createNamedParameter($this->getLastAdded($albumId), IQueryBuilder::PARAM_INT))
			->where($query->expr()->eq('album_id', $query->createNamedParameter($albumId, IQueryBuilder::PARAM_INT)))
			->executeStatement();
	}

	/**
	 * Remove a given file from any albums in which it was added by a given user.
	 */
	public function removeFileWithOwner(int $fileId, string $ownerId): void {
		// Get concerned albums before deleting them.
		$query = $this->connection->getQueryBuilder();
		$albumsRows = $query->select('album_id')
			->from('photos_albums_files')
			->where($query->expr()->eq('owner', $query->createNamedParameter($ownerId)))
			->andWhere($query->expr()->eq('file_id', $query->createNamedParameter($fileId, IQueryBuilder::PARAM_INT)))
			->executeQuery()
			->fetchAll();

		// Remove any occurrence of fileId when owner is ownerId.
		$query = $this->connection->getQueryBuilder();
		$query->delete('photos_albums_files')
			->where($query->expr()->eq('owner', $query->createNamedParameter($ownerId)))
			->andWhere($query->expr()->eq('file_id', $query->createNamedParameter($fileId, IQueryBuilder::PARAM_INT)))
			->executeStatement();

		// Update last_added_photo for concerned albums.
		foreach ($albumsRows as $row) {
			$query = $this->connection->getQueryBuilder();
			$query->update('photos_albums')
				->set('last_added_photo', $query->createNamedParameter($this->getLastAdded($row['album_id']), IQueryBuilder::PARAM_INT))
				->where($query->expr()->eq('album_id', $query->createNamedParameter($row['album_id'], IQueryBuilder::PARAM_INT)));
			$query->executeStatement();
		}
	}

	private function getLastAdded(int $albumId): int {
		$query = $this->connection->getQueryBuilder();
		$query->select('file_id')
			->from('photos_albums_files')
			->where($query->expr()->eq('album_id', $query->createNamedParameter($albumId, IQueryBuilder::PARAM_INT)))
			->orderBy('added', 'DESC')
			->setMaxResults(1);
		$id = $query->executeQuery()->fetchOne();
		if ($id === false) {
			return -1;
		} else {
			return (int)$id;
		}
	}

	/**
	 * @return array<array{'id': string, 'label': string, 'type': int}>
	 */
	public function getCollaborators(int $albumId): array {
		$query = $this->connection->getQueryBuilder();
		$query->select('collaborator_id', 'collaborator_type')
			->from('photos_albums_collabs')
			->where($query->expr()->eq('album_id', $query->createNamedParameter($albumId, IQueryBuilder::PARAM_INT)));

		$rows = $query->executeQuery()->fetchAll();

		$collaborators = array_map(function (array $row) {
			/** @var string|null */
			$displayName = null;

			$displayName = match ($row['collaborator_type']) {
				self::TYPE_USER => $this->userManager->get($row['collaborator_id'])?->getDisplayName(),
				self::TYPE_GROUP => $this->groupManager->get($row['collaborator_id'])?->getDisplayName(),
				self::TYPE_LINK => $this->l->t('Public link'),
				default => throw new \Exception('Invalid collaborator type: ' . $row['collaborator_type']),
			};

			if (is_null($displayName)) {
				return null;
			}

			return [
				'id' => $row['collaborator_id'],
				'label' => $displayName,
				'type' => (int)$row['collaborator_type'],
			];
		}, $rows);

		return array_values(array_filter($collaborators, fn ($c): bool => $c !== null));
	}


	public function isCollaborator(int $albumId, string $userId): bool {
		$query = $this->connection->getQueryBuilder();
		$query->select('collaborator_id', 'collaborator_type')
			->from('photos_albums_collabs')
			->where($query->expr()->eq('album_id', $query->createNamedParameter($albumId, IQueryBuilder::PARAM_INT)));

		$rows = $query->executeQuery()->fetchAll();

		foreach ($rows as $row) {
			switch ($row['collaborator_type']) {
				case self::TYPE_USER:
					if ($row['collaborator_id'] === $userId) {
						return true;
					}
					break;
				case self::TYPE_GROUP:
					if ($this->groupManager->isInGroup($userId, $row['collaborator_id'])) {
						return true;
					}
					break;
				default:
					break;
			}
		}

		return false;
	}

	/**
	 * @param array{'id': string, 'type': int} $collaborators
	 */
	public function setCollaborators(int $albumId, array $collaborators): void {
		$existingCollaborators = $this->getCollaborators($albumId);

		// Different behavior if type is link to prevent creating multiple link.
		$computeKey = (fn ($c): string => ($c['type'] === AlbumMapper::TYPE_LINK ? '' : $c['id']) . $c['type']);

		$collaboratorsToAdd = array_udiff($collaborators, $existingCollaborators, fn ($a, $b): int => strcmp($computeKey($a), $computeKey($b)));
		$collaboratorsToRemove = array_udiff($existingCollaborators, $collaborators, fn ($a, $b): int => strcmp($computeKey($a), $computeKey($b)));

		$this->connection->beginTransaction();

		foreach ($collaboratorsToAdd as $collaborator) {
			switch ($collaborator['type']) {
				case self::TYPE_USER:
					if (is_null($this->userManager->get($collaborator['id']))) {
						throw new \Exception('Unknown collaborator: ' . $collaborator['id']);
					}
					break;
				case self::TYPE_GROUP:
					if (is_null($this->groupManager->get($collaborator['id']))) {
						throw new \Exception('Unknown collaborator: ' . $collaborator['id']);
					}
					break;
				case self::TYPE_LINK:
					$collaborator['id'] = $this->random->generate(32, ISecureRandom::CHAR_UPPER . ISecureRandom::CHAR_LOWER . ISecureRandom::CHAR_DIGITS);
					break;
				default:
					throw new \Exception('Invalid collaborator type: ' . $collaborator['type']);
			}

			$query = $this->connection->getQueryBuilder();
			$query->insert('photos_albums_collabs')
				->setValue('album_id', $query->createNamedParameter($albumId, IQueryBuilder::PARAM_INT))
				->setValue('collaborator_id', $query->createNamedParameter($collaborator['id']))
				->setValue('collaborator_type', $query->createNamedParameter($collaborator['type'], IQueryBuilder::PARAM_INT))
				->executeStatement();
		}

		foreach ($collaboratorsToRemove as $collaborator) {
			switch ($collaborator['type']) {
				case self::TYPE_USER:
					$this->deleteUserFromAlbumCollaboratorsList($collaborator['id'], $albumId);
					break;
				default:
					$query = $this->connection->getQueryBuilder();
					$query->delete('photos_albums_collabs')
						->where($query->expr()->eq('album_id', $query->createNamedParameter($albumId, IQueryBuilder::PARAM_INT)))
						->andWhere($query->expr()->eq('collaborator_id', $query->createNamedParameter($collaborator['id'])))
						->andWhere($query->expr()->eq('collaborator_type', $query->createNamedParameter($collaborator['type'], IQueryBuilder::PARAM_INT)))
						->executeStatement();
			}
		}

		$this->connection->commit();
	}

	/**
	 * @return AlbumInfo[]
	 */
	public function getSharedAlbumsForCollaborator(string $collaboratorId, int $collaboratorType): array {
		$query = $this->connection->getQueryBuilder();
		$rows = $query
			->select('a.album_id', 'name', 'user', 'location', 'created', 'last_added_photo', 'filters')
			->from('photos_albums_collabs', 'c')
			->leftJoin('c', 'photos_albums', 'a', $query->expr()->eq('a.album_id', 'c.album_id'))
			->where($query->expr()->eq('collaborator_id', $query->createNamedParameter($collaboratorId)))
			->andWhere($query->expr()->eq('collaborator_type', $query->createNamedParameter($collaboratorType, IQueryBuilder::PARAM_INT)))
			->andWhere($query->expr()->isNotNull('a.album_id'))
			->executeQuery()
			->fetchAll();

		return array_map(fn (array $row): AlbumInfo => new AlbumInfo(
			(int)$row['album_id'],
			$row['user'],
			$row['name'] . ' (' . $row['user'] . ')',
			$row['location'],
			(int)$row['created'],
			(int)$row['last_added_photo'],
			$row['filters'],
		), $rows);
	}

	/**
	 * @return AlbumWithFiles[]
	 */
	public function getSharedAlbumsForCollaboratorWithFiles(string $collaboratorId, int $collaboratorType): array {
		$query = $this->connection->getQueryBuilder();
		$rows = $query
			->select('fileid', 'mimetype', 'a.album_id', 'size', 'mtime', 'etag', 'location', 'created', 'last_added_photo', 'added', 'owner', 'filters')
			->selectAlias('f.name', 'file_name')
			->selectAlias('a.name', 'album_name')
			->selectAlias('a.user', 'album_user')
			->from('photos_albums_collabs', 'c')
			->leftJoin('c', 'photos_albums', 'a', $query->expr()->eq('a.album_id', 'c.album_id'))
			->leftJoin('a', 'photos_albums_files', 'p', $query->expr()->eq('a.album_id', 'p.album_id'))
			->leftJoin('p', 'filecache', 'f', $query->expr()->eq('p.file_id', 'f.fileid'))
			->where($query->expr()->eq('collaborator_id', $query->createNamedParameter($collaboratorId)))
			->andWhere($query->expr()->eq('collaborator_type', $query->createNamedParameter($collaboratorType, IQueryBuilder::PARAM_INT)))
			->andWhere($query->expr()->isNotNull('a.album_id'))
			->executeQuery()
			->fetchAll();

		$filesByAlbum = [];
		$albumsById = [];
		foreach ($rows as $row) {
			$albumId = (int)$row['album_id'];
			if ($row['fileid']) {
				$mimeType = $this->mimeTypeLoader->getMimetypeById((int)$row['mimetype']);
				$filesByAlbum[$albumId][] = new AlbumFile(
					(int)$row['fileid'],
					$row['file_name'],
					$mimeType,
					(int)$row['size'],
					(int)$row['mtime'],
					$row['etag'],
					(int)$row['added'],
					$row['owner'],
					'user',
				);
			}

			if (!isset($albumsById[$albumId])) {
				$albumName = $row['album_name'];
				// Suffix album name with the album owner to prevent duplicates.
				// Not done for public link as it would leak the owner's uid.
				if ($collaboratorType !== self::TYPE_LINK) {
					$albumName = $row['album_name'] . ' (' . $row['album_user'] . ')';
				}
				$albumsById[$albumId] = new AlbumInfo(
					$albumId,
					$row['album_user'],
					$albumName,
					$row['location'],
					(int)$row['created'],
					(int)$row['last_added_photo'],
					$row['filters'],
					$collaboratorType,
				);
			}
		}

		$result = [];
		foreach ($albumsById as $id => $album) {
			$filesByAlbum[$id] ??= [];
			$fileIds = array_map(fn ($file) => $file->getFileId(), $filesByAlbum[$id]);

			$smartAlbumFiles = array_filter(
				$this->filtersManager->getFilesBasedOnFilters($album->getUserId(), $album->getDecodedFilters()),
				fn ($file) => array_search($file->getFileId(), $fileIds) === false,
			);

			$result[] = new AlbumWithFiles($album, $this, [...$filesByAlbum[$id], ...$smartAlbumFiles]);
		}
		return $result;
	}

	public function deleteUserFromAlbumCollaboratorsList(string $userId, int $albumId): void {
		$query = $this->connection->getQueryBuilder();
		$query->delete('photos_albums_collabs')
			->where($query->expr()->eq('album_id', $query->createNamedParameter($albumId, IQueryBuilder::PARAM_INT)))
			->andWhere($query->expr()->eq('collaborator_id', $query->createNamedParameter($userId)))
			->andWhere($query->expr()->eq('collaborator_type', $query->createNamedParameter(self::TYPE_USER, IQueryBuilder::PARAM_INT)))
			->executeStatement();

		// Remove all photos by this user from the album:
		$this->removeFilesForUser($albumId, $userId);
	}

	public function deleteGroupFromAlbumCollaboratorsList(string $groupId, int $albumId): void {
		$query = $this->connection->getQueryBuilder();
		$query->delete('photos_albums_collabs')
			->where($query->expr()->eq('album_id', $query->createNamedParameter($albumId, IQueryBuilder::PARAM_INT)))
			->andWhere($query->expr()->eq('collaborator_id', $query->createNamedParameter($groupId)))
			->andWhere($query->expr()->eq('collaborator_type', $query->createNamedParameter(self::TYPE_GROUP, IQueryBuilder::PARAM_INT)))
			->executeStatement();
	}

	/**
	 * @return AlbumInfo[]
	 */
	public function getAlbumsForCollaboratorIdAndFileId(string $collaboratorId, int $collaboratorType, int $fileId): array {
		$query = $this->connection->getQueryBuilder();
		$rows = $query
			->select('a.album_id', 'name', 'user', 'location', 'created', 'last_added_photo', 'filters')
			->from('photos_albums_collabs', 'c')
			->leftJoin('c', 'photos_albums', 'a', $query->expr()->eq('a.album_id', 'c.album_id'))
			->leftJoin('a', 'photos_albums_files', 'p', $query->expr()->eq('a.album_id', 'p.album_id'))
			->where($query->expr()->eq('collaborator_id', $query->createNamedParameter($collaboratorId)))
			->andWhere($query->expr()->eq('collaborator_type', $query->createNamedParameter($collaboratorType, IQueryBuilder::PARAM_INT)))
			->andWhere($query->expr()->eq('file_id', $query->createNamedParameter($fileId, IQueryBuilder::PARAM_INT)))
			->groupBy('a.album_id')
			->executeQuery()
			->fetchAll();


		return array_map(fn (array $row): AlbumInfo => new AlbumInfo(
			(int)$row['album_id'],
			$row['user'],
			$row['name'] . ' (' . $row['user'] . ')',
			$row['location'],
			(int)$row['created'],
			(int)$row['last_added_photo'],
			$row['filters'],
		), $rows);
	}

	public function setAlbumFilters(int $albumId, string $filters): void {
		$query = $this->connection->getQueryBuilder();
		$query->update('photos_albums')
			->set('filters', $query->createNamedParameter($filters))
			->where($query->expr()->eq('album_id', $query->createNamedParameter($albumId, IQueryBuilder::PARAM_STR)));
		$query->executeStatement();
	}
}