mirror of https://github.com/nextcloud/bookmarks
Refactor DB Mappers
* work with tree table * introduce QueryParameters * introduce return type hints
This commit is contained in:
parent
b2e95bc6f3
commit
f5f6f3ceca
|
@ -14,7 +14,9 @@
|
|||
|
||||
namespace OCA\Bookmarks\AppInfo;
|
||||
|
||||
use OCA\Bookmarks\Db\FolderMapper;
|
||||
use OCP\AppFramework\App;
|
||||
use OCP\EventDispatcher\IEventDispatcher;
|
||||
use OCP\IContainer;
|
||||
use OCP\IUser;
|
||||
|
||||
|
@ -34,5 +36,10 @@ class Application extends App {
|
|||
$container->registerService('request', function ($c) {
|
||||
return $c->query('Request');
|
||||
});
|
||||
|
||||
$dispatcher = $this->getContainer()->query(IEventDispatcher::class);
|
||||
$dispatcher->addServiceListener('\OCA\Bookmarks::onBookmarkDelete', FolderMapper::class);
|
||||
$dispatcher->addServiceListener('\OCA\Bookmarks::onBookmarkUpdate', FolderMapper::class);
|
||||
$dispatcher->addServiceListener('\OCA\Bookmarks::onBookmarkCreate', FolderMapper::class);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,21 +2,23 @@
|
|||
|
||||
namespace OCA\Bookmarks\Db;
|
||||
|
||||
use GuzzleHttp\Query;
|
||||
use OCA\Bookmarks\Exception\AlreadyExistsError;
|
||||
use OCA\Bookmarks\Exception\UrlParseError;
|
||||
use OCA\Bookmarks\Exception\UserLimitExceededError;
|
||||
use OCA\Bookmarks\QueryParameters;
|
||||
use OCA\Bookmarks\Service\UrlNormalizer;
|
||||
use OCP\AppFramework\Db\DoesNotExistException;
|
||||
use OCP\AppFramework\Db\Entity;
|
||||
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
|
||||
use OCP\AppFramework\Db\QBMapper;
|
||||
use OCP\DB\QueryBuilder\IQueryBuilder;
|
||||
use OCP\EventDispatcher\Event;
|
||||
use OCP\ICache;
|
||||
use OCP\ICacheFactory;
|
||||
use OCP\IConfig;
|
||||
use OCP\IDBConnection;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\EventDispatcher\GenericEvent;
|
||||
use OCP\EventDispatcher\IEventDispatcher;
|
||||
|
||||
/**
|
||||
* Class BookmarkMapper
|
||||
|
@ -28,7 +30,7 @@ class BookmarkMapper extends QBMapper {
|
|||
/** @var IConfig */
|
||||
private $config;
|
||||
|
||||
/** @var EventDispatcherInterface */
|
||||
/** @var IEventDispatcher */
|
||||
private $eventDispatcher;
|
||||
|
||||
/** @var UrlNormalizer */
|
||||
|
@ -58,19 +60,19 @@ class BookmarkMapper extends QBMapper {
|
|||
* BookmarkMapper constructor.
|
||||
*
|
||||
* @param IDBConnection $db
|
||||
* @param EventDispatcherInterface $eventDispatcher
|
||||
* @param IEventDispatcher $eventDispatcher
|
||||
* @param UrlNormalizer $urlNormalizer
|
||||
* @param IConfig $config
|
||||
* @param PublicFolderMapper $publicMapper
|
||||
* @param TagMapper $tagMapper
|
||||
* @param ICacheFactory $cacheFactory
|
||||
*/
|
||||
public function __construct(IDBConnection $db, EventDispatcherInterface $eventDispatcher, UrlNormalizer $urlNormalizer, IConfig $config, PublicFolderMapper $publicMapper, TagMapper $tagMapper, ICacheFactory $cacheFactory) {
|
||||
public function __construct(IDBConnection $db, IEventDispatcher $eventDispatcher, UrlNormalizer $urlNormalizer, IConfig $config, PublicFolderMapper $publicMapper, TagMapper $tagMapper, ICacheFactory $cacheFactory) {
|
||||
parent::__construct($db, 'bookmarks', Bookmark::class);
|
||||
$this->eventDispatcher = $eventDispatcher;
|
||||
$this->urlNormalizer = $urlNormalizer;
|
||||
$this->config = $config;
|
||||
$this->limit = intval($config->getAppValue('bookmarks', 'performance.maxBookmarksperAccount', 0));
|
||||
$this->limit = (int)$config->getAppValue('bookmarks', 'performance.maxBookmarksperAccount', 0);
|
||||
$this->publicMapper = $publicMapper;
|
||||
$this->tagMapper = $tagMapper;
|
||||
$this->cache = $cacheFactory->createLocal('bookmarks:hashes');
|
||||
|
@ -117,15 +119,12 @@ class BookmarkMapper extends QBMapper {
|
|||
/**
|
||||
* @param $userId
|
||||
* @param array $filters
|
||||
* @param string $conjunction
|
||||
* @param string $sortBy
|
||||
* @param int $offset
|
||||
* @param int $limit
|
||||
* @param QueryParameters $params
|
||||
* @return array|Entity[]
|
||||
*/
|
||||
public function findAll($userId, array $filters, string $conjunction = 'and', string $sortBy = 'lastmodified', int $offset = 0, int $limit = -1) {
|
||||
public function findAll($userId, array $filters, QueryParameters $params): array {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$bookmark_cols = array_map(function ($c) {
|
||||
$bookmark_cols = array_map(static function ($c) {
|
||||
return 'b.' . $c;
|
||||
}, Bookmark::$columns);
|
||||
|
||||
|
@ -143,18 +142,33 @@ class BookmarkMapper extends QBMapper {
|
|||
->orWhere($qb->expr()->eq('p.user_id', $qb->createPositionalParameter($userId)));
|
||||
|
||||
|
||||
$this->_findBookmarksBuildFilter($qb, $filters, $conjunction);
|
||||
$this->_queryBuilderSortAndPaginate($qb, $sortBy, $offset, $limit);
|
||||
$this->_findBookmarksBuildFilter($qb, $filters, $params);
|
||||
$this->_queryBuilderSortAndPaginate($qb, $params);
|
||||
|
||||
return $this->findEntities($qb);
|
||||
}
|
||||
|
||||
private function _queryBuilderSortAndPaginate(IQueryBuilder $qb, string $sortBy = 'lastmodified', int $offset = 0, int $limit = -1) {
|
||||
if (!in_array($sortBy, Bookmark::$columns)) {
|
||||
$sqlSortColumn = 'lastmodified';
|
||||
} else {
|
||||
$sqlSortColumn = $sortBy;
|
||||
}
|
||||
/**
|
||||
* @param $userId
|
||||
* @return int
|
||||
*/
|
||||
public function countAll($userId): int {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
|
||||
$qb->select($qb->func()->count('b.id'));
|
||||
|
||||
// Finds bookmarks in 2-levels nested shares only
|
||||
$qb
|
||||
->from('bookmarks', 'b')
|
||||
->where($qb->expr()->eq('b.user_id', $qb->createPositionalParameter($userId)));
|
||||
|
||||
$count = $qb->execute()->fetch(\PDO::FETCH_COLUMN)[0];
|
||||
|
||||
return (int) $count;
|
||||
}
|
||||
|
||||
private function _queryBuilderSortAndPaginate(IQueryBuilder $qb, QueryParameters $params): void {
|
||||
$sqlSortColumn = $params->getSortBy('lastmodified', Bookmark::$columns);
|
||||
|
||||
if ($sqlSortColumn === 'title') {
|
||||
$qb->orderBy($qb->createFunction('UPPER(`title`)'), 'ASC');
|
||||
|
@ -162,36 +176,35 @@ class BookmarkMapper extends QBMapper {
|
|||
$qb->orderBy($sqlSortColumn, 'DESC');
|
||||
}
|
||||
|
||||
if ($limit !== -1) {
|
||||
$qb->setMaxResults($limit);
|
||||
if ($params->getLimit() !== -1) {
|
||||
$qb->setMaxResults($params->getLimit());
|
||||
}
|
||||
if ($offset !== 0) {
|
||||
$qb->setFirstResult($offset);
|
||||
if ($params->getOffset() !== 0) {
|
||||
$qb->setFirstResult($params->getOffset());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param IQueryBuilder $qb
|
||||
* @param array $filters
|
||||
* @param string $tagFilterConjunction
|
||||
* @param QueryParameters $params
|
||||
*/
|
||||
private function _findBookmarksBuildFilter(&$qb, $filters, $tagFilterConjunction) {
|
||||
private function _findBookmarksBuildFilter(&$qb, $filters, QueryParameters $params): void {
|
||||
$dbType = $this->config->getSystemValue('dbtype', 'sqlite');
|
||||
$connectWord = 'AND';
|
||||
if ($tagFilterConjunction === 'or') {
|
||||
if ($params->getConjunction() === 'or') {
|
||||
$connectWord = 'OR';
|
||||
}
|
||||
if (count($filters) === 0) {
|
||||
return;
|
||||
}
|
||||
if ($dbType === 'pgsql') {
|
||||
$tags = $qb->createFunction("array_to_string(array_agg(" . $qb->getColumnName('t.tag') . "), ',')");
|
||||
$tags = $qb->createFunction('array_to_string(array_agg(' . $qb->getColumnName('t.tag') . "), ',')");
|
||||
} else {
|
||||
$tags = $qb->createFunction('GROUP_CONCAT(' . $qb->getColumnName('t.tag') . ')');
|
||||
}
|
||||
$filterExpressions = [];
|
||||
$otherColumns = ['b.url', 'b.title', 'b.description'];
|
||||
$i = 0;
|
||||
foreach ($filters as $filter) {
|
||||
$expr = [];
|
||||
$expr[] = $qb->expr()->iLike($tags, $qb->createPositionalParameter('%' . $this->db->escapeLikeParameter($filter) . '%'));
|
||||
|
@ -202,7 +215,6 @@ class BookmarkMapper extends QBMapper {
|
|||
);
|
||||
}
|
||||
$filterExpressions[] = call_user_func_array([$qb->expr(), 'orX'], $expr);
|
||||
$i++;
|
||||
}
|
||||
if ($connectWord === 'AND') {
|
||||
$filterExpression = call_user_func_array([$qb->expr(), 'andX'], $filterExpressions);
|
||||
|
@ -215,12 +227,10 @@ class BookmarkMapper extends QBMapper {
|
|||
/**
|
||||
* @param string $userId
|
||||
* @param string $tag
|
||||
* @param string $sortBy
|
||||
* @param int $offset
|
||||
* @param int $limit
|
||||
* @param QueryParameters $params
|
||||
* @return array|Entity[]
|
||||
*/
|
||||
public function findByTag($userId, string $tag, string $sortBy = 'lastmodified', int $offset = 0, int $limit = 10) {
|
||||
public function findByTag($userId, string $tag, QueryParameters $params): array {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select(Bookmark::$columns);
|
||||
|
||||
|
@ -230,21 +240,12 @@ class BookmarkMapper extends QBMapper {
|
|||
->where($qb->expr()->eq('user_id', $qb->createPositionalParameter($userId)))
|
||||
->andWhere($qb->expr()->eq('t.tag', $qb->createPositionalParameter($tag)));
|
||||
|
||||
$this->_queryBuilderSortAndPaginate($qb, $sortBy, $offset, $limit);
|
||||
$this->_queryBuilderSortAndPaginate($qb, $params);
|
||||
|
||||
return $this->findEntities($qb);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $userId
|
||||
* @param array $tags
|
||||
* @param string $sortBy
|
||||
* @param int $offset
|
||||
* @param int $limit
|
||||
* @return array|Entity[]
|
||||
*/
|
||||
public function findByTags($userId, array $tags, string $sortBy = 'lastmodified', int $offset = 0, int $limit = 10) {
|
||||
$dbType = $this->config->getSystemValue('dbtype', 'sqlite');
|
||||
private function _findByTags($userId): IQueryBuilder {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select(Bookmark::$columns);
|
||||
|
||||
|
@ -253,8 +254,21 @@ class BookmarkMapper extends QBMapper {
|
|||
->leftJoin('b', 'bookmarks_tags', 't', $qb->expr()->eq('t.bookmark_id', 'b.id'))
|
||||
->where($qb->expr()->eq('user_id', $qb->createPositionalParameter($userId)));
|
||||
|
||||
return $qb;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $userId
|
||||
* @param array $tags
|
||||
* @param QueryParameters $params
|
||||
* @return array|Entity[]
|
||||
*/
|
||||
public function findByTags($userId, array $tags, QueryParameters $params): array {
|
||||
$qb = $this->_findByTags($userId);
|
||||
|
||||
$dbType = $this->config->getSystemValue('dbtype', 'sqlite');
|
||||
if ($dbType === 'pgsql') {
|
||||
$tagsCol = $qb->createFunction("array_to_string(array_agg(" . $qb->getColumnName('t.tag') . "), ',')");
|
||||
$tagsCol = $qb->createFunction('array_to_string(array_agg(' . $qb->getColumnName('t.tag') . "), ',')");
|
||||
} else {
|
||||
$tagsCol = $qb->createFunction('GROUP_CONCAT(' . $qb->getColumnName('t.tag') . ')');
|
||||
}
|
||||
|
@ -267,38 +281,30 @@ class BookmarkMapper extends QBMapper {
|
|||
$qb->groupBy(...Bookmark::$columns);
|
||||
$qb->having($filterExpression);
|
||||
|
||||
$this->_queryBuilderSortAndPaginate($qb, $sortBy, $offset, $limit);
|
||||
$this->_queryBuilderSortAndPaginate($qb, $params);
|
||||
|
||||
return $this->findEntities($qb);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $userId
|
||||
* @param string $sortBy
|
||||
* @param int $offset
|
||||
* @param int $limit
|
||||
* @param QueryParameters $params
|
||||
* @return array|Entity[]
|
||||
*/
|
||||
public function findUntagged($userId, string $sortBy = 'lastmodified', int $offset = 0, int $limit = 10) {
|
||||
public function findUntagged($userId, QueryParameters $params): array {
|
||||
$qb = $this->_findByTags($userId);
|
||||
|
||||
$dbType = $this->config->getSystemValue('dbtype', 'sqlite');
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select(Bookmark::$columns);
|
||||
|
||||
$qb
|
||||
->from('bookmarks', 'b')
|
||||
->leftJoin('b', 'bookmarks_tags', 't', $qb->expr()->eq('t.bookmark_id', 'b.id'))
|
||||
->where($qb->expr()->eq('user_id', $qb->createPositionalParameter($userId)));
|
||||
|
||||
if ($dbType === 'pgsql') {
|
||||
$tagsCol = $qb->createFunction("array_to_string(array_agg(" . $qb->getColumnName('t.tag') . "), ',')");
|
||||
$tagsCol = $qb->createFunction('array_to_string(array_agg(' . $qb->getColumnName('t.tag') . "), ',')");
|
||||
} else {
|
||||
$tagsCol = $qb->createFunction('GROUP_CONCAT(' . $qb->getColumnName('t.tag') . ')');
|
||||
}
|
||||
|
||||
$qb->groupBy(...Bookmark::$columns);
|
||||
$qb->having($qb->expr()->eql($tagsCol, $qb->createPositionalParameter('')));
|
||||
$qb->having($qb->expr()->eq($tagsCol, $qb->createPositionalParameter('')));
|
||||
|
||||
$this->_queryBuilderSortAndPaginate($qb, $sortBy, $offset, $limit);
|
||||
$this->_queryBuilderSortAndPaginate($qb, $params);
|
||||
return $this->findEntities($qb);
|
||||
}
|
||||
|
||||
|
@ -306,32 +312,27 @@ class BookmarkMapper extends QBMapper {
|
|||
*
|
||||
* @param $token
|
||||
* @param array $filters
|
||||
* @param string $conjunction
|
||||
* @param string $sortBy
|
||||
* @param int $offset
|
||||
* @param int $limit
|
||||
* @param QueryParameters $params
|
||||
* @return array|Entity[]
|
||||
* @throws DoesNotExistException
|
||||
* @throws MultipleObjectsReturnedException
|
||||
*/
|
||||
public function findAllInPublicFolder($token, array $filters, string $conjunction = 'and', string $sortBy = 'lastmodified', int $offset = 0, int $limit = 10) {
|
||||
public function findAllInPublicFolder($token, array $filters, QueryParameters $params): array {
|
||||
$publicFolder = $this->publicMapper->find($token);
|
||||
|
||||
$bookmarks = $this->findByFolder($publicFolder->getFolderId(), $sortBy, $offset, $limit);
|
||||
$bookmarks = $this->findByFolder($publicFolder->getFolderId(), $params);
|
||||
// Really inefficient, but what can you do.
|
||||
return array_filter($bookmarks, function ($bookmark) use ($filters, $conjunction) {
|
||||
|
||||
return array_filter($bookmarks, function (Bookmark $bookmark) use ($filters, $params) {
|
||||
$tagsFound = $this->tagMapper->findByBookmark($bookmark->getId());
|
||||
return array_reduce($filters, function ($isMatch, $filter) use ($bookmark, $tagsFound, $conjunction) {
|
||||
return array_reduce($filters, static function ($isMatch, $filter) use ($bookmark, $tagsFound, $params) {
|
||||
$filter = strtolower($filter);
|
||||
|
||||
|
||||
$res = in_array($filter, $tagsFound)
|
||||
$res = in_array($filter, $tagsFound, true)
|
||||
|| str_contains($filter, strtolower($bookmark->getTitle()))
|
||||
|| str_contains($filter, strtolower($bookmark->getDescription()))
|
||||
|| str_contains($filter, strtolower($bookmark->getUrl()));
|
||||
return $conjunction === 'and' ? $res && $isMatch : $res || $isMatch;
|
||||
}, $conjunction === 'and' ? true : false);
|
||||
return $params->getConjunction() === 'and' ? $res && $isMatch : $res || $isMatch;
|
||||
}, $params->getConjunction() === 'and');
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -339,23 +340,20 @@ class BookmarkMapper extends QBMapper {
|
|||
*
|
||||
* @param $token
|
||||
* @param array $tags
|
||||
* @param string $sortBy
|
||||
* @param int $offset
|
||||
* @param int $limit
|
||||
* @param QueryParameters $params
|
||||
* @return array|Entity[]
|
||||
* @throws DoesNotExistException
|
||||
* @throws MultipleObjectsReturnedException
|
||||
*/
|
||||
public function findByTagsInPublicFolder($token, array $tags = [], string $sortBy = 'lastmodified', int $offset = 0, int $limit = 10) {
|
||||
public function findByTagsInPublicFolder($token, array $tags, QueryParameters $params): array {
|
||||
$publicFolder = $this->publicMapper->find($token);
|
||||
|
||||
$bookmarks = $this->findByFolder($publicFolder->getFolderId(), $sortBy, $offset, $limit);
|
||||
$bookmarks = $this->findByFolder($publicFolder->getFolderId(), $params);
|
||||
// Really inefficient, but what can you do.
|
||||
return array_filter($bookmarks, function ($bookmark) use ($tags) {
|
||||
|
||||
return array_filter($bookmarks, function (Bookmark $bookmark) use ($tags) {
|
||||
$tagsFound = $this->tagMapper->findByBookmark($bookmark->getId());
|
||||
return array_reduce($tags, function ($isFound, $tag) use ($tagsFound) {
|
||||
return in_array($tag, $tagsFound) && $isFound;
|
||||
return array_reduce($tags, static function ($isFound, $tag) use ($tagsFound) {
|
||||
return in_array($tag, $tagsFound, true) && $isFound;
|
||||
}, true);
|
||||
});
|
||||
}
|
||||
|
@ -363,20 +361,17 @@ class BookmarkMapper extends QBMapper {
|
|||
/**
|
||||
*
|
||||
* @param $token
|
||||
* @param string $sortBy
|
||||
* @param int $offset
|
||||
* @param int $limit
|
||||
* @param QueryParameters $params
|
||||
* @return array|Entity[]
|
||||
* @throws DoesNotExistException
|
||||
* @throws MultipleObjectsReturnedException
|
||||
*/
|
||||
public function findUntaggedInPublicFolder($token, string $sortBy = 'lastmodified', int $offset = 0, int $limit = 10) {
|
||||
public function findUntaggedInPublicFolder($token, QueryParameters $params): array {
|
||||
$publicFolder = $this->publicMapper->find($token);
|
||||
|
||||
$bookmarks = $this->findByFolder($publicFolder->getFolderId(), $sortBy, $offset, $limit);
|
||||
$bookmarks = $this->findByFolder($publicFolder->getFolderId(), $params);
|
||||
// Really inefficient, but what can you do.
|
||||
return array_filter($bookmarks, function ($bookmark) {
|
||||
|
||||
return array_filter($bookmarks, function (Bookmark $bookmark) {
|
||||
$tags = $this->tagMapper->findByBookmark($bookmark->getId());
|
||||
return count($tags) === 0;
|
||||
});
|
||||
|
@ -384,53 +379,17 @@ class BookmarkMapper extends QBMapper {
|
|||
|
||||
|
||||
/**
|
||||
* @param $userId
|
||||
* @param int $folderId
|
||||
* @param string $sortBy
|
||||
* @param int $offset
|
||||
* @param int $limit
|
||||
* @param QueryParameters $params
|
||||
* @return array|Entity[]
|
||||
*/
|
||||
public function findByUserFolder($userId, int $folderId, string $sortBy = 'lastmodified', int $offset = 0, int $limit = 10) {
|
||||
if ($folderId !== -1) {
|
||||
return $this->findByFolder($folderId, $sortBy, $offset, $limit);
|
||||
} else {
|
||||
return $this->findByRootFolder($userId, $sortBy, $offset, $limit);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $folderId
|
||||
* @param string $sortBy
|
||||
* @param int $offset
|
||||
* @param int $limit
|
||||
* @return array|Entity[]
|
||||
*/
|
||||
public function findByFolder(int $folderId, string $sortBy = 'lastmodified', int $offset = 0, int $limit = 10) {
|
||||
public function findByFolder(int $folderId, QueryParameters $params): array {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select(Bookmark::$columns)
|
||||
->from('bookmarks', 'b')
|
||||
->leftJoin('b', 'bookmarks_folders_bookmarks', 'f', $qb->expr()->eq('f.bookmark_id', 'b.id'))
|
||||
->where($qb->expr()->eq('f.folder_id', $qb->createPositionalParameter($folderId)));
|
||||
$this->_queryBuilderSortAndPaginate($qb, $sortBy, $offset, $limit);
|
||||
return $this->findEntities($qb);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $userId
|
||||
* @param string $sortBy
|
||||
* @param int $offset
|
||||
* @param int $limit
|
||||
* @return array|Entity[]
|
||||
*/
|
||||
public function findByRootFolder($userId, string $sortBy = 'lastmodified', int $offset = 0, int $limit = 10) {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select(Bookmark::$columns)
|
||||
->from('bookmarks', 'b')
|
||||
->leftJoin('b', 'bookmarks_folders_bookmarks', 'f', $qb->expr()->eq('f.bookmark_id', 'b.id'))
|
||||
->where($qb->expr()->eq('f.folder_id', $qb->createPositionalParameter(-1)))
|
||||
->andWhere($qb->expr()->eq('b.user_id', $qb->createPositionalParameter($userId)));
|
||||
$this->_queryBuilderSortAndPaginate($qb, $sortBy, $offset, $limit);
|
||||
$this->_queryBuilderSortAndPaginate($qb, $params);
|
||||
return $this->findEntities($qb);
|
||||
}
|
||||
|
||||
|
@ -439,7 +398,7 @@ class BookmarkMapper extends QBMapper {
|
|||
* @param int $stalePeriod
|
||||
* @return array|Entity[]
|
||||
*/
|
||||
public function findPendingPreviews(int $limit, int $stalePeriod) {
|
||||
public function findPendingPreviews(int $limit, int $stalePeriod): array {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select('*');
|
||||
$qb->from('bookmarks', 'b');
|
||||
|
@ -457,7 +416,6 @@ class BookmarkMapper extends QBMapper {
|
|||
$returnedEntity = parent::delete($entity);
|
||||
|
||||
$id = $entity->getId();
|
||||
$userId = $entity->getUserId();
|
||||
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb
|
||||
|
@ -473,9 +431,11 @@ class BookmarkMapper extends QBMapper {
|
|||
|
||||
$this->eventDispatcher->dispatch(
|
||||
'\OCA\Bookmarks::onBookmarkDelete',
|
||||
new GenericEvent(null, ['id' => $id, 'userId' => $userId])
|
||||
new Event($entity)
|
||||
);
|
||||
|
||||
$this->invalidateCache($entity->getId());
|
||||
|
||||
return $returnedEntity;
|
||||
}
|
||||
|
||||
|
@ -486,10 +446,7 @@ class BookmarkMapper extends QBMapper {
|
|||
*/
|
||||
public function update(Entity $entity): Entity {
|
||||
// normalize url
|
||||
|
||||
|
||||
$entity->setUrl($this->urlNormalizer->normalize($entity->getUrl()));
|
||||
|
||||
$entity->setLastmodified(time());
|
||||
|
||||
$newEntity = parent::update($entity);
|
||||
|
@ -497,9 +454,11 @@ class BookmarkMapper extends QBMapper {
|
|||
// trigger event
|
||||
$this->eventDispatcher->dispatch(
|
||||
'\OCA\Bookmarks::onBookmarkUpdate',
|
||||
new GenericEvent(null, ['id' => $entity->getId(), 'userId' => $entity->getUserId()])
|
||||
new Event($entity)
|
||||
);
|
||||
|
||||
$this->invalidateCache($entity->getId());
|
||||
|
||||
return $newEntity;
|
||||
}
|
||||
|
||||
|
@ -512,23 +471,19 @@ class BookmarkMapper extends QBMapper {
|
|||
*/
|
||||
public function insert(Entity $entity): Entity {
|
||||
// Enforce user limit
|
||||
if ($this->limit > 0 && $this->limit <= count($this->findAll($entity->getUserId(), []))) {
|
||||
throw new UserLimitExceededError();
|
||||
if ($this->limit > 0 && $this->limit <= $this->countAll($entity->getUserId())) {
|
||||
throw new UserLimitExceededError('Exceeded user limit of ' . $this->limit . ' bookmarks');
|
||||
}
|
||||
|
||||
// normalize url
|
||||
|
||||
|
||||
$entity->setUrl($this->urlNormalizer->normalize($entity->getUrl()));
|
||||
|
||||
if ($entity->getAdded() === null) $entity->setAdded(time());
|
||||
|
||||
if ($entity->getAdded() === null) {
|
||||
$entity->setAdded(time());
|
||||
}
|
||||
$entity->setLastmodified(time());
|
||||
|
||||
$entity->setAdded(time());
|
||||
|
||||
$entity->setLastPreview(0);
|
||||
|
||||
$entity->setClickcount(0);
|
||||
|
||||
$exists = true;
|
||||
|
@ -547,10 +502,10 @@ class BookmarkMapper extends QBMapper {
|
|||
|
||||
parent::insert($entity);
|
||||
|
||||
|
||||
$this->invalidateCache($entity->getId());
|
||||
$this->eventDispatcher->dispatch(
|
||||
'\OCA\Bookmarks::onBookmarkCreate',
|
||||
new GenericEvent(null, ['id' => $entity->getId(), 'userId' => $entity->getUserId()])
|
||||
new Event($entity->getId())
|
||||
);
|
||||
return $entity;
|
||||
}
|
||||
|
@ -589,11 +544,11 @@ class BookmarkMapper extends QBMapper {
|
|||
* @throws DoesNotExistException
|
||||
* @throws MultipleObjectsReturnedException
|
||||
*/
|
||||
public function hash(int $bookmarkId, array $fields) {
|
||||
public function hash(int $bookmarkId, array $fields): string {
|
||||
$key = $this->getCacheKey($bookmarkId);
|
||||
$hash = $this->cache->get($key);
|
||||
$selector = implode(',', $fields);
|
||||
if (isset($hash) && isset($hash[$selector])) {
|
||||
if (isset($hash[$selector])) {
|
||||
return $hash[$selector];
|
||||
}
|
||||
if (!isset($hash)) {
|
||||
|
@ -608,7 +563,7 @@ class BookmarkMapper extends QBMapper {
|
|||
}
|
||||
}
|
||||
$hash[$selector] = hash('sha256', json_encode($bookmark, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
|
||||
$this->cache->set($key, $hash, 60*60*24);
|
||||
$this->cache->set($key, $hash, 60 * 60 * 24);
|
||||
return $hash[$selector];
|
||||
}
|
||||
|
||||
|
@ -616,14 +571,14 @@ class BookmarkMapper extends QBMapper {
|
|||
* @param int $bookmarkId
|
||||
* @return string
|
||||
*/
|
||||
private function getCacheKey(int $bookmarkId) {
|
||||
private function getCacheKey(int $bookmarkId): string {
|
||||
return 'bm:' . $bookmarkId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $bookmarkId
|
||||
*/
|
||||
public function invalidateCache(int $bookmarkId) {
|
||||
public function invalidateCache(int $bookmarkId): void {
|
||||
$key = $this->getCacheKey($bookmarkId);
|
||||
$this->cache->remove($key);
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
namespace OCA\Bookmarks\Db;
|
||||
|
||||
use OCA\Bookmarks\Exception\ChildrenOrderValidationError;
|
||||
use OCA\Bookmarks\Exception\UnauthorizedAccessError;
|
||||
use OCA\Bookmarks\Exception\UnsupportedOperation;
|
||||
use OCP\AppFramework\Db\DoesNotExistException;
|
||||
use OCP\AppFramework\Db\Entity;
|
||||
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
|
||||
|
@ -12,16 +12,19 @@ use OCP\ICache;
|
|||
use OCP\ICacheFactory;
|
||||
use OCP\IDBConnection;
|
||||
use UnexpectedValueException;
|
||||
use OCP\EventDispatcher\Event;
|
||||
use OCP\EventDispatcher\IEventListener;
|
||||
|
||||
/**
|
||||
* Class FolderMapper
|
||||
*
|
||||
* @package OCA\Bookmarks\Db
|
||||
*/
|
||||
class FolderMapper extends QBMapper {
|
||||
class FolderMapper extends QBMapper implements IEventListener {
|
||||
|
||||
const TYPE_BOOKMARK = 'bookmark';
|
||||
const TYPE_FOLDER = 'folder';
|
||||
public const TYPE_SHARE = 'share';
|
||||
public const TYPE_FOLDER = 'folder';
|
||||
public const TYPE_BOOKMARK = 'bookmark';
|
||||
|
||||
/**
|
||||
* @var BookmarkMapper
|
||||
|
@ -44,6 +47,8 @@ class FolderMapper extends QBMapper {
|
|||
*/
|
||||
protected $cache;
|
||||
|
||||
protected $cachedFolders;
|
||||
|
||||
/**
|
||||
* FolderMapper constructor.
|
||||
*
|
||||
|
@ -59,6 +64,7 @@ class FolderMapper extends QBMapper {
|
|||
$this->shareMapper = $shareMapper;
|
||||
$this->sharedFolderMapper = $sharedFolderMapper;
|
||||
$this->cache = $cacheFactory->createLocal('bookmarks:hashes');
|
||||
$this->cachedFolders = [];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -68,75 +74,80 @@ class FolderMapper extends QBMapper {
|
|||
* @throws MultipleObjectsReturnedException if more than one result
|
||||
*/
|
||||
public function find(int $id): Entity {
|
||||
if (isset($this->cachedFolders[$id]) && $this->cachedFolders[$id] !== null) {
|
||||
return $this->cachedFolders[$id];
|
||||
}
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb
|
||||
->select('*')
|
||||
->from('bookmarks_folders')
|
||||
->where($qb->expr()->eq('id', $qb->createNamedParameter($id)));
|
||||
|
||||
$this->cachedFolders[$id] = $this->findEntity($qb);
|
||||
return $this->cachedFolders[$id];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $userId
|
||||
* @return Entity
|
||||
* @throws DoesNotExistException
|
||||
* @throws MultipleObjectsReturnedException
|
||||
*/
|
||||
public function findRootFolder($userId): Entity {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb
|
||||
->select(Folder::$columns)
|
||||
->from('bookmarks_folders', 'f')
|
||||
->join('f', 'bookmarks_root_folders', 't', $qb->expr()->eq('id', 'folder_id'))
|
||||
->where($qb->expr()->eq('user_id', $qb->createNamedParameter($userId)));
|
||||
return $this->findEntity($qb);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $userId
|
||||
* @param int $folderId
|
||||
* @return array|Entity[]
|
||||
*/
|
||||
public function findChildFolders(int $folderId): array {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb
|
||||
->select(Folder::$columns)
|
||||
->from('bookmarks_folders', 'f')
|
||||
->join('f', 'bookmarks_tree', 't', $qb->expr()->eq('t.id', 'f.id'))
|
||||
->where($qb->expr()->eq('t.parent_folder', $qb->createPositionalParameter($folderId)))
|
||||
->andWhere($qb->expr()->eq('t.type', self::TYPE_FOLDER))
|
||||
->orderBy('t.index', 'ASC');
|
||||
return $this->findEntities($qb);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $folderId
|
||||
* @return Entity
|
||||
* @throws DoesNotExistException
|
||||
* @throws MultipleObjectsReturnedException
|
||||
* @throws UnauthorizedAccessError
|
||||
*/
|
||||
public function findByUserFolder($userId, int $folderId) {
|
||||
if ($folderId !== -1) {
|
||||
if ($this->find($folderId) !== $userId) {
|
||||
throw new UnauthorizedAccessError();
|
||||
}
|
||||
return $this->findByParentFolder($folderId);
|
||||
} else {
|
||||
return $this->findByRootFolder($userId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $folderId
|
||||
* @return array|Entity[]
|
||||
*/
|
||||
public function findByParentFolder(int $folderId) {
|
||||
public function findParentOfFolder(int $folderId): Entity {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb
|
||||
->select('*')
|
||||
->from('bookmarks_folders')
|
||||
->where($qb->expr()->eq('parent_folder', $qb->createPositionalParameter($folderId)))
|
||||
->orderBy('title', 'DESC');
|
||||
return $this->findEntities($qb);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $userId
|
||||
* @return array|Entity[]
|
||||
*/
|
||||
public function findByRootFolder($userId) {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb
|
||||
->select('*')
|
||||
->from('bookmarks_folders')
|
||||
->where($qb->expr()->eq('user_id', $qb->createPositionalParameter($userId)))
|
||||
->andWhere($qb->expr()->eq('parent_folder', $qb->createPositionalParameter(-1)))
|
||||
->orderBy('title', 'DESC');
|
||||
return $this->findEntities($qb);
|
||||
->select(Folder::$columns)
|
||||
->from('bookmarks_folders', 'f')
|
||||
->join('f', 'bookmarks_tree', 't', $qb->expr()->eq('t.parent_folder', 'f.id'))
|
||||
->where($qb->expr()->eq('t.id', $qb->createPositionalParameter($folderId)))
|
||||
->andWhere($qb->expr()->eq('type', self::TYPE_FOLDER));
|
||||
return $this->findEntity($qb);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $folderId
|
||||
* @return array|Entity[]
|
||||
*/
|
||||
public function findByAncestorFolder($folderId) {
|
||||
public function findByAncestorFolder($folderId): array {
|
||||
$descendants = [];
|
||||
$newDescendants = $this->findByParentFolder($folderId);
|
||||
$newDescendants = $this->findChildFolders($folderId);
|
||||
do {
|
||||
$newDescendants = array_flatten(array_map(function ($descendant) {
|
||||
return $this->findByParentFolder($descendant);
|
||||
return $this->findChildFolders($descendant);
|
||||
}, $newDescendants));
|
||||
array_push($descendants, $newDescendants);
|
||||
$descendants[] = $newDescendants;
|
||||
} while (count($newDescendants) > 0);
|
||||
return $descendants;
|
||||
}
|
||||
|
@ -145,46 +156,34 @@ class FolderMapper extends QBMapper {
|
|||
* @param $folderId
|
||||
* @param $descendantFolderId
|
||||
* @return bool
|
||||
* @throws DoesNotExistException
|
||||
* @throws MultipleObjectsReturnedException
|
||||
*/
|
||||
public function hasDescendantFolder($folderId, $descendantFolderId) {
|
||||
$descendant = $this->find($descendantFolderId);
|
||||
public function hasDescendantFolder($folderId, $descendantFolderId): bool {
|
||||
do {
|
||||
$descendant = $this->find($descendant->getParentFolder());
|
||||
} while ($descendant->getId() !== $folderId && $descendant->getParentFolder() !== -1);
|
||||
return ($descendant->getId() === $folderId);
|
||||
try {
|
||||
$descendant = $this->findParentOfFolder($descendantFolderId);
|
||||
} catch (DoesNotExistException $e) {
|
||||
return false;
|
||||
}
|
||||
} while ($descendant->getId() !== $folderId);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $bookmarkId
|
||||
* @return array|Entity[]
|
||||
*/
|
||||
public function findByBookmark(int $bookmarkId) {
|
||||
public function findParentsOfBookmark(int $bookmarkId): array {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select(Folder::$columns);
|
||||
|
||||
$qb
|
||||
->from('bookmarks_folders', 'f')
|
||||
->innerJoin('f', 'bookmarks_folders_bookmarks', 'b', $qb->expr()->eq('b.folder_id', 'f.id'))
|
||||
->where($qb->expr()->eq('b.bookmark_id', $qb->createPositionalParameter($bookmarkId)));
|
||||
->join('f', 'bookmarks_tree', 't', $qb->expr()->eq('t.id', 'f.id'))
|
||||
->where($qb->expr()->eq('t.id', $qb->createPositionalParameter($bookmarkId)))
|
||||
->andWhere($qb->expr()->eq('t.type', self::TYPE_BOOKMARK));
|
||||
|
||||
$entities = $this->findEntities($qb);
|
||||
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select('*');
|
||||
$qb
|
||||
->from('bookmarks_folders_bookmarks')
|
||||
->where($qb->expr()->eq('bookmark_id', $qb->createPositionalParameter($bookmarkId)))
|
||||
->andWhere($qb->expr()->eq('folder_id', $qb->createPositionalParameter(-1)));
|
||||
|
||||
if ($qb->execute()->fetch()) {
|
||||
$root = new Folder();
|
||||
$root->setId(-1);
|
||||
array_push($entities, $root);
|
||||
}
|
||||
|
||||
return $entities;
|
||||
return $this->findEntities($qb);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -192,20 +191,20 @@ class FolderMapper extends QBMapper {
|
|||
* @param $descendantBookmarkId
|
||||
* @return bool
|
||||
*/
|
||||
public function hasDescendantBookmark($folderId, $descendantBookmarkId) {
|
||||
$newAncestors = $this->findByBookmark($descendantBookmarkId);
|
||||
do {
|
||||
foreach ($newAncestors as $ancestor) {
|
||||
if ($ancestor->getId() === $folderId) {
|
||||
public function hasDescendantBookmark($folderId, $descendantBookmarkId): bool {
|
||||
$newAncestors = $this->findParentsOfBookmark($descendantBookmarkId);
|
||||
foreach ($newAncestors as $ancestor) {
|
||||
if ($ancestor->getId() === $folderId) {
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
if ($this->hasDescendantFolder($folderId, $ancestor->getId())) {
|
||||
return true;
|
||||
}
|
||||
} catch (MultipleObjectsReturnedException $e) {
|
||||
continue;
|
||||
}
|
||||
$newAncestors = array_map(function ($ancestor) {
|
||||
return $this->find($ancestor->getParentFolder());
|
||||
}, array_filter($newAncestors, function ($ancestor) {
|
||||
return $ancestor->getParentFolder() !== -1 && $ancestor->getId() !== -1;
|
||||
}));
|
||||
} while (count($newAncestors) > 0);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -214,86 +213,75 @@ class FolderMapper extends QBMapper {
|
|||
* @return Entity
|
||||
*/
|
||||
public function delete(Entity $entity): Entity {
|
||||
$childFolders = $this->findByParentFolder($entity->id);
|
||||
$childFolders = $this->findChildFolders($entity->getId());
|
||||
foreach ($childFolders as $folder) {
|
||||
$this->delete($folder);
|
||||
}
|
||||
$childBookmarks = $this->bookmarkMapper->findByFolder($entity->id);
|
||||
foreach ($childBookmarks as $bookmark) {
|
||||
$this->bookmarkMapper->delete($bookmark);
|
||||
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb
|
||||
->select(array_merge(Bookmark::$columns, [$qb->func()->count('t2.parent_folder', 'parent_count')]))
|
||||
->from('bookmarks', 'b')
|
||||
->join('b', 'bookmarks_tree', 't1', $qb->expr()->eq('t1.id', 'b.id'))
|
||||
->join('t', 'bookmarks_tree', 't2', $qb->expr()->eq('t1.id', 't2.id'))
|
||||
->where($qb->expr()->eq('t2.parent_folder', $qb->createPositionalParameter($entity->getId())))
|
||||
->andWhere($qb->expr()->eq('t1.type', self::TYPE_BOOKMARK))
|
||||
->andWhere($qb->expr()->eq('t1.type', self::TYPE_BOOKMARK))
|
||||
->andWhere($qb->expr()->eq('t2.type', self::TYPE_BOOKMARK))
|
||||
->andWhere($qb->expr()->lte('parent_count', 1));
|
||||
$bookmarks = $qb->execute();
|
||||
|
||||
foreach ($bookmarks as $bookmarkId) {
|
||||
try {
|
||||
$this->bookmarkMapper->delete($this->bookmarkMapper->find($bookmarkId));
|
||||
} catch (DoesNotExistException $e) {
|
||||
continue;
|
||||
} catch (MultipleObjectsReturnedException $e) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb
|
||||
->delete('bookmarks_tree', 't')
|
||||
->where($qb->expr()->eq('t.parent_folder', $qb->createPositionalParameter($entity->getId())))
|
||||
->andWhere($qb->expr()->eq('t.type', self::TYPE_BOOKMARK));
|
||||
$qb->execute();
|
||||
|
||||
$this->cachedFolders[$entity->getId()] = null;
|
||||
$this->invalidateCache($entity->getUserId(), $entity->getId());
|
||||
return parent::delete($entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $userId
|
||||
* @throws DoesNotExistException
|
||||
* @throws MultipleObjectsReturnedException
|
||||
*/
|
||||
public function deleteAll($userId) {
|
||||
$childFolders = $this->findByRootFolder($userId);
|
||||
foreach ($childFolders as $folder) {
|
||||
$this->delete($folder);
|
||||
}
|
||||
$childBookmarks = $this->bookmarkMapper->findByRootFolder($userId);
|
||||
foreach ($childBookmarks as $bookmark) {
|
||||
$this->bookmarkMapper->delete($bookmark);
|
||||
}
|
||||
$rootFolder = $this->findRootFolder($userId);
|
||||
$this->delete($rootFolder);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Entity $entity
|
||||
* @return Entity
|
||||
* @throws DoesNotExistException
|
||||
* @throws MultipleObjectsReturnedException
|
||||
*/
|
||||
public function update(Entity $entity): Entity {
|
||||
if ($entity->getParentFolder() !== -1) {
|
||||
$this->find($entity->getParentFolder());
|
||||
}
|
||||
$this->cachedFolders[$entity->getId()] = $entity;
|
||||
$this->invalidateCache($entity->getUserId(), $entity->getId());
|
||||
return parent::update($entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Entity $entity
|
||||
* @return Entity
|
||||
* @throws DoesNotExistException
|
||||
* @throws MultipleObjectsReturnedException
|
||||
*/
|
||||
public function insertOrUpdate(Entity $entity): Entity {
|
||||
if ($entity->getParentFolder() !== -1) {
|
||||
$this->find($entity->getParentFolder());
|
||||
}
|
||||
return parent::insertOrUpdate($entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Entity $entity
|
||||
* @return Entity
|
||||
* @throws DoesNotExistException
|
||||
* @throws MultipleObjectsReturnedException
|
||||
*/
|
||||
public function insert(Entity $entity): Entity {
|
||||
if ($entity->getParentFolder() !== -1) {
|
||||
$this->find($entity->getParentFolder());
|
||||
}
|
||||
return parent::insert($entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Lists bookmark folders' child folders (helper)
|
||||
* @param $userId
|
||||
* @param int $folderId
|
||||
* @param array $newChildrenOrder
|
||||
* @return void
|
||||
* @throws ChildrenOrderValidationError
|
||||
*/
|
||||
public function setUserFolderChildren($userId, int $folderId, array $newChildrenOrder) {
|
||||
if ($folderId !== -1) {
|
||||
$this->setChildren($folderId, $newChildrenOrder);
|
||||
return;
|
||||
} else {
|
||||
$this->setRootChildren($userId, $newChildrenOrder);
|
||||
return;
|
||||
}
|
||||
parent::insert($entity);
|
||||
$this->cachedFolders[$entity->getId()] = $entity;
|
||||
$this->invalidateCache($entity->getUserId(), $entity->getId());
|
||||
return $entity;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -303,143 +291,62 @@ class FolderMapper extends QBMapper {
|
|||
* @return void
|
||||
* @throws ChildrenOrderValidationError
|
||||
*/
|
||||
public function setChildren(int $folderId, array $newChildrenOrder) {
|
||||
public function setChildren(int $folderId, array $newChildrenOrder): void {
|
||||
try {
|
||||
$folder = $this->find($folderId);
|
||||
} catch (DoesNotExistException $e) {
|
||||
throw new ChildrenOrderValidationError();
|
||||
throw new ChildrenOrderValidationError('Folder not found');
|
||||
} catch (MultipleObjectsReturnedException $e) {
|
||||
throw new ChildrenOrderValidationError();
|
||||
throw new ChildrenOrderValidationError('Multiple folders found');
|
||||
}
|
||||
$existingChildren = $this->getChildren($folderId);
|
||||
foreach ($existingChildren as $child) {
|
||||
if (!in_array($child, $newChildrenOrder)) {
|
||||
throw new ChildrenOrderValidationError();
|
||||
if (!in_array($child, $newChildrenOrder, true)) {
|
||||
throw new ChildrenOrderValidationError('A child is missing');
|
||||
}
|
||||
if (!isset($child['id'], $child['type'])) {
|
||||
throw new ChildrenOrderValidationError();
|
||||
throw new ChildrenOrderValidationError('A child item is missing properties');
|
||||
}
|
||||
}
|
||||
if (count($newChildrenOrder) !== count($existingChildren)) {
|
||||
throw new ChildrenOrderValidationError();
|
||||
throw new ChildrenOrderValidationError('To many children');
|
||||
}
|
||||
foreach ($newChildrenOrder as $i => $child) {
|
||||
switch ($child['type']) {
|
||||
case'bookmark':
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb
|
||||
->update('bookmarks_folders_bookmarks')
|
||||
->set('index', $qb->createPositionalParameter($i))
|
||||
->where($qb->expr()->eq('bookmark_id', $qb->createPositionalParameter($child['id'])))
|
||||
->andWhere($qb->expr()->eq('folder_id', $qb->createPositionalParameter($folderId)));
|
||||
$qb->execute();
|
||||
break;
|
||||
case 'folder':
|
||||
try {
|
||||
$childFolder = $this->find($child['id']);
|
||||
} catch (DoesNotExistException $e) {
|
||||
throw new ChildrenOrderValidationError();
|
||||
} catch (MultipleObjectsReturnedException $e) {
|
||||
throw new ChildrenOrderValidationError();
|
||||
}
|
||||
if ($childFolder->getUserId() !== $folder->getUserId()) {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb
|
||||
->update('bookmarks_shared')
|
||||
->innerJoin('p', 'bookmarks_shares', 's', 's.id = p.share_id')
|
||||
->set('index', $qb->createPositionalParameter($i))
|
||||
->where($qb->expr()->eq('s.folder_id', $qb->createPositionalParameter($child['id'])))
|
||||
->andWhere($qb->expr()->eq('parent_folder', $qb->createPositionalParameter(-1)));
|
||||
$qb->execute();
|
||||
break;
|
||||
}
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb
|
||||
->update('bookmarks_folders')
|
||||
->set('index', $qb->createPositionalParameter($i))
|
||||
->where($qb->expr()->eq('id', $qb->createPositionalParameter($child['id'])))
|
||||
->andWhere($qb->expr()->eq('parent_folder', $qb->createPositionalParameter($folderId)));
|
||||
$qb->execute();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Lists bookmark folders' child folders (helper)
|
||||
* @param $userId
|
||||
* @param array $newChildrenOrder
|
||||
* @return void
|
||||
* @throws ChildrenOrderValidationError
|
||||
*/
|
||||
public function setRootChildren($userId, array $newChildrenOrder) {
|
||||
$existingChildren = $this->getRootChildren($userId);
|
||||
foreach ($existingChildren as $child) {
|
||||
if (!in_array($child, $newChildrenOrder)) {
|
||||
throw new ChildrenOrderValidationError();
|
||||
}
|
||||
if (!isset($child['id'], $child['type'])) {
|
||||
throw new ChildrenOrderValidationError();
|
||||
}
|
||||
}
|
||||
if (count($newChildrenOrder) !== count($existingChildren)) {
|
||||
throw new ChildrenOrderValidationError();
|
||||
}
|
||||
foreach ($newChildrenOrder as $i => $child) {
|
||||
switch ($child['type']) {
|
||||
case'bookmark':
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb
|
||||
->update('bookmarks_folders_bookmarks')
|
||||
->set('index', $qb->createPositionalParameter($i))
|
||||
->where($qb->expr()->eq('bookmark_id', $qb->createPositionalParameter($child['id'])))
|
||||
->andWhere($qb->expr()->eq('folder_id', $qb->createPositionalParameter(-1)));
|
||||
$qb->execute();
|
||||
break;
|
||||
case 'folder':
|
||||
try {
|
||||
$folder = $this->find($child['id']);
|
||||
} catch (DoesNotExistException $e) {
|
||||
throw new ChildrenOrderValidationError();
|
||||
} catch (MultipleObjectsReturnedException $e) {
|
||||
throw new ChildrenOrderValidationError();
|
||||
}
|
||||
if ($folder->getUserId() !== $userId) {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb
|
||||
->update('bookmarks_shared')
|
||||
->innerJoin('p', 'bookmarks_shares', 's', 's.id = p.share_id')
|
||||
->set('index', $qb->createPositionalParameter($i))
|
||||
->where($qb->expr()->eq('s.folder_id', $qb->createPositionalParameter($child['id'])))
|
||||
->andWhere($qb->expr()->eq('parent_folder', $qb->createPositionalParameter(-1)));
|
||||
$qb->execute();
|
||||
break;
|
||||
}
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb
|
||||
->update('bookmarks_folders')
|
||||
->set('index', $qb->createPositionalParameter($i))
|
||||
->where($qb->expr()->eq('id', $qb->createPositionalParameter($child['id'])))
|
||||
->andWhere($qb->expr()->eq('parent_folder', $qb->createPositionalParameter(-1)));
|
||||
$qb->execute();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb
|
||||
->select('folder_id', 'share_id')
|
||||
->from('bookmarks_shares', 's')
|
||||
->innerJoin('s', 'bookmarks_tree', 't', $qb->expr()->eq('t.id', 's.id'))
|
||||
->where($qb->expr()->eq('t.parent_folder', $qb->createPositionalParameter($folderId)))
|
||||
->where($qb->expr()->eq('t.type', self::TYPE_SHARE))
|
||||
->orderBy('t.index', 'ASC');
|
||||
$childShares = $qb->execute()->fetchAll();
|
||||
|
||||
/**
|
||||
* @brief Lists bookmark folders' child folders (helper)
|
||||
* @param $userId
|
||||
* @param int $folderId
|
||||
* @param int $layers
|
||||
* @return array
|
||||
*/
|
||||
public function getUserFolderChildren($userId, int $folderId, int $layers) {
|
||||
if ($folderId !== -1) {
|
||||
return $this->getChildren($folderId, $layers);
|
||||
} else {
|
||||
return $this->getRootChildren($userId, $layers);
|
||||
$foldersToShares = array_reduce($childShares, static function ($dict, $shareRec) {
|
||||
$dict[$shareRec['folder_id']] = $shareRec['share_id'];
|
||||
return $dict;
|
||||
}, []);
|
||||
|
||||
foreach ($newChildrenOrder as $i => $child) {
|
||||
if (!in_array($child['type'], [self::TYPE_FOLDER, self::TYPE_BOOKMARK], true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (($child['type'] === self::TYPE_FOLDER) && isset($foldersToShares[$child['id']])) {
|
||||
$child['type'] = self::TYPE_SHARE;
|
||||
$child['id'] = $foldersToShares[$child['id']];
|
||||
}
|
||||
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb
|
||||
->update('bookmarks_tree')
|
||||
->set('index', $qb->createPositionalParameter($i))
|
||||
->where($qb->expr()->eq('id', $qb->createPositionalParameter($child['id'])))
|
||||
->andWhere($qb->expr()->eq('parent_folder', $qb->createPositionalParameter($folderId)))
|
||||
->andWhere($qb->expr()->eq('type', $child['type']));
|
||||
$qb->execute();
|
||||
}
|
||||
$this->invalidateCache($folder->getUserId(), $folderId);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -448,96 +355,41 @@ class FolderMapper extends QBMapper {
|
|||
* @param int $layers The amount of levels to return
|
||||
* @return array the children each in the format ["id" => int, "type" => 'bookmark' | 'folder' ]
|
||||
*/
|
||||
public function getChildren($folderId, $layers = 1) {
|
||||
public function getChildren($folderId, $layers = 1): array {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb
|
||||
->select('id', 'title', 'parent_folder', 'index')
|
||||
->from('bookmarks_folders')
|
||||
->select('id', 'type', 'index')
|
||||
->from('bookmarks_tree')
|
||||
->andWhere($qb->expr()->eq('parent_folder', $qb->createPositionalParameter($folderId)))
|
||||
->orderBy('index', 'ASC');
|
||||
$childFolders = $qb->execute()->fetchAll();
|
||||
$children = $qb->execute()->fetchAll();
|
||||
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb
|
||||
->select('folder_id', 'index')
|
||||
->from('bookmarks_shares', 's')
|
||||
->innerJoin('s', 'bookmarks_shared', 'p', $qb->expr()->eq('s.id', 'p.share_id'))
|
||||
->where($qb->expr()->eq('p.parent_folder', $qb->createPositionalParameter($folderId)))
|
||||
->orderBy('index', 'ASC');
|
||||
->innerJoin('s', 'bookmarks_tree', 't', $qb->expr()->eq('t.id', 's.id'))
|
||||
->where($qb->expr()->eq('t.parent_folder', $qb->createPositionalParameter($folderId)))
|
||||
->where($qb->expr()->eq('t.type', self::TYPE_SHARE))
|
||||
->orderBy('t.index', 'ASC');
|
||||
$childShares = $qb->execute()->fetchAll();
|
||||
|
||||
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb
|
||||
->select('bookmark_id', 'index')
|
||||
->from('bookmarks_folders_bookmarks', 'f')
|
||||
->innerJoin('f', 'bookmarks', 'b', $qb->expr()->eq('b.id', 'f.bookmark_id'))
|
||||
->where($qb->expr()->eq('folder_id', $qb->createPositionalParameter($folderId)))
|
||||
->orderBy('index', 'ASC');
|
||||
$childBookmarks = $qb->execute()->fetchAll();
|
||||
|
||||
|
||||
return $this->_getChildren($childFolders, $childShares, $childBookmarks, $layers);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Lists bookmark folders' child folders (helper)
|
||||
* @param $userId
|
||||
* @param int $layers The amount of levels to return
|
||||
* @return array the children each in the format ["id" => int, "type" => 'bookmark' | 'folder' ]
|
||||
*/
|
||||
public function getRootChildren($userId, $layers = 1) {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb
|
||||
->select('id', 'title', 'parent_folder', 'index')
|
||||
->from('bookmarks_folders')
|
||||
->where($qb->expr()->eq('user_id', $qb->createPositionalParameter($userId)))
|
||||
->andWhere($qb->expr()->eq('parent_folder', $qb->createPositionalParameter(-1)))
|
||||
->orderBy('index', 'ASC');
|
||||
$childFolders = $qb->execute()->fetchAll();
|
||||
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb
|
||||
->select('folder_id', 'index')
|
||||
->from('bookmarks_shares', 's')
|
||||
->innerJoin('s', 'bookmarks_shared', 'p', $qb->expr()->eq('s.id', 'p.share_id'))
|
||||
->where($qb->expr()->eq('p.parent_folder', $qb->createPositionalParameter(-1)))
|
||||
->andWhere($qb->expr()->eq('p.user_id', $qb->createPositionalParameter($userId)))
|
||||
->orderBy('index', 'ASC');
|
||||
$childShares = $qb->execute()->fetchAll();
|
||||
|
||||
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb
|
||||
->select('bookmark_id', 'index')
|
||||
->from('bookmarks_folders_bookmarks', 'f')
|
||||
->innerJoin('f', 'bookmarks', 'b', $qb->expr()->eq('b.id', 'f.bookmark_id'))
|
||||
->where($qb->expr()->eq('folder_id', $qb->createPositionalParameter(-1)))
|
||||
->andWhere($qb->expr()->eq('b.user_id', $qb->createPositionalParameter($userId)))
|
||||
->orderBy('index', 'ASC');
|
||||
$childBookmarks = $qb->execute()->fetchAll();
|
||||
|
||||
return $this->_getChildren($childFolders, $childShares, $childBookmarks, $layers);
|
||||
}
|
||||
|
||||
private function _getChildren($childFolders, $childShares, $childBookmarks, $layers): array {
|
||||
$children = array_merge($childFolders, $childShares, $childBookmarks);
|
||||
array_multisort(array_column($children, 'index'), \SORT_ASC, $children);
|
||||
$children = array_map(function ($child) use ($layers) {
|
||||
$children = array_map(function ($child) use ($layers, $childShares) {
|
||||
if (isset($child['bookmark_id'])) {
|
||||
return ['type' => self::TYPE_BOOKMARK, 'id' => (int) $child['bookmark_id']];
|
||||
} else {
|
||||
$id = isset($child['id']) ? $child['id'] : $child['folder_id'];
|
||||
if ($layers === 1) {
|
||||
return ['type' => self::TYPE_FOLDER, 'id' => (int) $id];
|
||||
} else {
|
||||
return [
|
||||
'type' => self::TYPE_FOLDER,
|
||||
'id' => (int) $id,
|
||||
'children' => $this->getChildren($id, $layers - 1),
|
||||
];
|
||||
}
|
||||
return ['type' => self::TYPE_BOOKMARK, 'id' => (int)$child['bookmark_id']];
|
||||
}
|
||||
|
||||
$item = $item = ['type' => $child['type'], 'id' => $child['id']];
|
||||
|
||||
if ($item['type'] === self::TYPE_SHARE) {
|
||||
$item['type'] = 'folder';
|
||||
$item['id'] = array_shift($childShares)['folder_id'];
|
||||
}
|
||||
|
||||
if ($item['type'] === self::TYPE_FOLDER && $layers > 1) {
|
||||
$item['children'] = $this->getChildren($item['id'], $layers - 1);
|
||||
}
|
||||
return $item;
|
||||
}, $children);
|
||||
return $children;
|
||||
}
|
||||
|
@ -547,7 +399,7 @@ class FolderMapper extends QBMapper {
|
|||
* @param $folderId
|
||||
* @return string
|
||||
*/
|
||||
private function getCacheKey(string $userId, int $folderId) {
|
||||
private function getCacheKey(string $userId, int $folderId) : string {
|
||||
return 'folder:' . $userId . ',' . $folderId;
|
||||
}
|
||||
|
||||
|
@ -565,7 +417,8 @@ class FolderMapper extends QBMapper {
|
|||
// Invalidate parent
|
||||
try {
|
||||
$folder = $this->find($folderId);
|
||||
$this->invalidateCache($userId, $folder->getParentFolder());
|
||||
$parentFolder = $this->findParentOfFolder($folderId);
|
||||
$this->invalidateCache($userId, $parentFolder->getId());
|
||||
} catch (DoesNotExistException $e) {
|
||||
return;
|
||||
} catch (MultipleObjectsReturnedException $e) {
|
||||
|
@ -578,7 +431,7 @@ class FolderMapper extends QBMapper {
|
|||
|
||||
// invalidate shared folders
|
||||
$sharedFolders = $this->sharedFolderMapper->findByFolder($folderId);
|
||||
foreach($sharedFolders as $sharedFolder) {
|
||||
foreach ($sharedFolders as $sharedFolder) {
|
||||
$this->invalidateCache($sharedFolder->getUserId(), $folderId);
|
||||
}
|
||||
|
||||
|
@ -592,11 +445,11 @@ class FolderMapper extends QBMapper {
|
|||
* @throws DoesNotExistException
|
||||
* @throws MultipleObjectsReturnedException
|
||||
*/
|
||||
public function hashFolder(string $userId, int $folderId, $fields = ['title', 'url']) {
|
||||
public function hashFolder(string $userId, int $folderId, $fields = ['title', 'url']) : string {
|
||||
$key = $this->getCacheKey($userId, $folderId);
|
||||
$hash = $this->cache->get($key);
|
||||
$selector = implode(',', $fields);
|
||||
if (isset($hash) && isset($hash[$selector])) {
|
||||
if (isset($hash[$selector])) {
|
||||
return $hash[$selector];
|
||||
}
|
||||
if (!isset($hash)) {
|
||||
|
@ -624,41 +477,7 @@ class FolderMapper extends QBMapper {
|
|||
$folder['children'] = $childHashes;
|
||||
$hash[$selector] = hash('sha256', json_encode($folder, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
|
||||
|
||||
$this->cache->set($key, $hash, 60*60*24);
|
||||
return $hash[$selector];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $userId
|
||||
* @param array $fields
|
||||
* @return string
|
||||
*/
|
||||
public function hashRootFolder($userId, $fields = ['title', 'url']) {
|
||||
$key = $this->getCacheKey($userId, -1);
|
||||
$hash = $this->cache->get($key);
|
||||
$selector = implode(',', $fields);
|
||||
if (isset($hash) && isset($hash[$selector])) {
|
||||
return $hash[$selector];
|
||||
}
|
||||
if (!isset($hash)) {
|
||||
$hash = [];
|
||||
}
|
||||
|
||||
$children = $this->getRootChildren($userId);
|
||||
$childHashes = array_map(function ($item) use ($fields, $userId) {
|
||||
switch ($item['type']) {
|
||||
case self::TYPE_BOOKMARK:
|
||||
return $this->bookmarkMapper->hash($item['id'], $fields);
|
||||
case self::TYPE_FOLDER:
|
||||
return $this->hashFolder($userId, $item['id'], $fields);
|
||||
default:
|
||||
throw new UnexpectedValueException('Expected bookmark or folder, but not ' . $item['type']);
|
||||
}
|
||||
}, $children);
|
||||
$folder = [];
|
||||
$folder['children'] = $childHashes;
|
||||
$hash[$selector] = hash('sha256', json_encode($folder, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
|
||||
$this->cache->set($key, $hash, 60*60*24);
|
||||
$this->cache->set($key, $hash, 60 * 60 * 24);
|
||||
return $hash[$selector];
|
||||
}
|
||||
|
||||
|
@ -667,19 +486,20 @@ class FolderMapper extends QBMapper {
|
|||
* @param int $layers
|
||||
* @return array
|
||||
*/
|
||||
public function getSubFolders($root = -1, $layers = 0) {
|
||||
public function getSubFolders($root, $layers = 0) : array {
|
||||
$folders = array_map(function (Folder $folder) use ($layers) {
|
||||
$array = $folder->toArray();
|
||||
if ($layers - 1 != 0) {
|
||||
if ($layers - 1 !== 0) {
|
||||
$array['children'] = $this->getSubFolders($folder->getId(), $layers - 1);
|
||||
}
|
||||
return $array;
|
||||
}, $this->findByParentFolder($root));
|
||||
$shares = array_map(function (Share $folder) use ($layers) {
|
||||
}, $this->findChildFolders($root));
|
||||
$shares = array_map(function (SharedFolder $folder) use ($layers) {
|
||||
$share = $this->shareMapper->find($folder->getShareId());
|
||||
$array = $folder->toArray();
|
||||
$array['id'] = $share->getFolderId();
|
||||
if ($layers - 1 != 0) {
|
||||
$array['userId'] = $share->getUserId();
|
||||
if ($layers - 1 !== 0) {
|
||||
$array['children'] = $this->getSubFolders($share->getFolderId(), $layers - 1);
|
||||
}
|
||||
return $array;
|
||||
|
@ -690,6 +510,40 @@ class FolderMapper extends QBMapper {
|
|||
return $folders;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $folderId
|
||||
* @param int $newParentFolderId
|
||||
* @throws DoesNotExistException
|
||||
* @throws MultipleObjectsReturnedException
|
||||
* @throws UnsupportedOperation
|
||||
*/
|
||||
public function move(int $folderId, int $newParentFolderId) {
|
||||
$folder = $this->find($folderId);
|
||||
try {
|
||||
$currentParent = $this->findParentOfFolder($folderId);
|
||||
}catch(DoesNotExistException $e) {
|
||||
$currentParent = null;
|
||||
}
|
||||
$newParent = $this->find($newParentFolderId);
|
||||
if (isset($currentParent)) {
|
||||
$this->invalidateCache($folder->getUserId(), $currentParent->getId());
|
||||
}
|
||||
$this->invalidateCache($folder->getUserId(), $newParent->getId());
|
||||
if ($folder->getUserId() !== $newParent->getUserId()) {
|
||||
throw new UnsupportedOperation('Cannot move between user trees');
|
||||
}
|
||||
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb
|
||||
->update('bookmarks_tree')
|
||||
->values([
|
||||
'parent_folder' => $qb->createPositionalParameter($newParentFolderId),
|
||||
])
|
||||
->where($qb->expr()->eq('id', $qb->createPositionalParameter($folderId)))
|
||||
->andWhere($qb->expr()->eq('type', self::TYPE_FOLDER));
|
||||
$qb->execute();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Add a bookmark to a set of folders
|
||||
|
@ -703,14 +557,12 @@ class FolderMapper extends QBMapper {
|
|||
return;
|
||||
}
|
||||
|
||||
$currentFolders = $this->findByBookmark($bookmarkId);
|
||||
|
||||
$currentFolders = $this->findParentsOfBookmark($bookmarkId);
|
||||
$this->addToFolders($bookmarkId, $folders);
|
||||
|
||||
$this->removeFromFolders($bookmarkId, array_map(function ($f) {
|
||||
$this->removeFromFolders($bookmarkId, array_map(static function (Folder $f) {
|
||||
return $f->getId();
|
||||
}, array_filter($currentFolders, function ($folder) use ($folders) {
|
||||
return !in_array($folder->getId(), $folders);
|
||||
}, array_filter($currentFolders, static function (Folder $folder) use ($folders) {
|
||||
return !in_array($folder->getId(), $folders, true);
|
||||
})));
|
||||
}
|
||||
|
||||
|
@ -722,34 +574,31 @@ class FolderMapper extends QBMapper {
|
|||
* @throws MultipleObjectsReturnedException
|
||||
*/
|
||||
public function addToFolders(int $bookmarkId, array $folders) {
|
||||
$bookmark = $this->bookmarkMapper->find($bookmarkId);
|
||||
$this->bookmarkMapper->find($bookmarkId);
|
||||
$currentFolders = array_map(static function (Folder $f) {
|
||||
return $f->getId();
|
||||
}, $this->findParentsOfBookmark($bookmarkId));
|
||||
|
||||
$folders = array_filter($folders, static function ($folderId) use ($currentFolders) {
|
||||
return !in_array($folderId, $currentFolders, true);
|
||||
});
|
||||
|
||||
foreach ($folders as $folderId) {
|
||||
// check if folder exists
|
||||
if ($folderId !== -1 && $folderId !== '-1') {
|
||||
$folder = $this->find($folderId);
|
||||
}
|
||||
|
||||
// check if this folder<->bookmark mapping already exists
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb
|
||||
->select('*')
|
||||
->from('bookmarks_folders_bookmarks')
|
||||
->where($qb->expr()->eq('bookmark_id', $qb->createNamedParameter($bookmarkId)))
|
||||
->andWhere($qb->expr()->eq('folder_id', $qb->createNamedParameter($folderId)));
|
||||
|
||||
if ($qb->execute()->fetch()) {
|
||||
continue;
|
||||
}
|
||||
$folder = $this->find($folderId);
|
||||
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb
|
||||
->insert('bookmarks_folders_bookmarks')
|
||||
->insert('bookmarks_tree')
|
||||
->values([
|
||||
'folder_id' => $qb->createNamedParameter($folderId),
|
||||
'bookmark_id' => $qb->createNamedParameter($bookmarkId),
|
||||
'index' => $folderId !== -1 ? count($this->getChildren($folderId)) : count($this->getRootChildren($bookmark->getUserId())),
|
||||
'parent_folder' => $qb->createNamedParameter($folderId),
|
||||
'type' => self::TYPE_BOOKMARK,
|
||||
'id' => $qb->createNamedParameter($bookmarkId),
|
||||
'index' => $this->countChildren($folderId),
|
||||
]);
|
||||
$qb->execute();
|
||||
|
||||
$this->invalidateCache($folder->getUserId(), $folderId);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -763,37 +612,51 @@ class FolderMapper extends QBMapper {
|
|||
public function removeFromFolders(int $bookmarkId, array $folders) {
|
||||
$bm = $this->bookmarkMapper->find($bookmarkId);
|
||||
|
||||
$foldersLeft = count($this->findByBookmark($bookmarkId));
|
||||
$foldersLeft = count($this->findParentsOfBookmark($bookmarkId));
|
||||
|
||||
foreach ($folders as $folderId) {
|
||||
// check if folder exists
|
||||
if ($folderId !== -1 && $folderId !== '-1') {
|
||||
$this->find($folderId);
|
||||
}
|
||||
|
||||
// check if this folder<->bookmark mapping exists
|
||||
foreach ($folders as $folder) {
|
||||
$folderId = $folder->getId();
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb
|
||||
->select('*')
|
||||
->from('bookmarks_folders_bookmarks')
|
||||
->where($qb->expr()->eq('bookmark_id', $qb->createNamedParameter($bookmarkId)))
|
||||
->andWhere($qb->expr()->eq('folder_id', $qb->createNamedParameter($folderId)));
|
||||
|
||||
if (!$qb->execute()->fetch()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb
|
||||
->delete('bookmarks_folders_bookmarks')
|
||||
->where($qb->expr()->eq('folder_id', $qb->createNamedParameter($folderId)))
|
||||
->andwhere($qb->expr()->eq('bookmark_id', $qb->createNamedParameter($bookmarkId)));
|
||||
->delete('bookmarks_tree')
|
||||
->where($qb->expr()->eq('parent_folder', $qb->createPositionalParameter($folderId)))
|
||||
->andWhere($qb->expr()->eq('id', $qb->createPositionalParameter($bookmarkId)))
|
||||
->andWhere($qb->expr()->eq('t.type', self::TYPE_BOOKMARK));
|
||||
$qb->execute();
|
||||
|
||||
$this->invalidateCache($folder->getUserId(), $folderId);
|
||||
|
||||
$foldersLeft--;
|
||||
}
|
||||
if ($foldersLeft <= 0) {
|
||||
$this->bookmarkMapper->delete($bm);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Count the children in the given folder
|
||||
* @param int $folderId
|
||||
* @return mixed
|
||||
*/
|
||||
public function countChildren(int $folderId) {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb
|
||||
->select($qb->func()->count('index', 'count'))
|
||||
->from('bookmarks_tree')
|
||||
->where($qb->expr()->eq('parent_folder', $qb->createPositionalParameter($folderId)));
|
||||
return $qb->execute()->fetch(\PDO::FETCH_COLUMN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle onBookmark{Create,Update,Delete} events
|
||||
*
|
||||
* @param Event $event
|
||||
*/
|
||||
public function handle(Event $event): void {
|
||||
$bookmark = $event->getSubject();
|
||||
$folders = $this->findParentsOfBookmark($bookmark->getId());
|
||||
foreach($folders as $folder) {
|
||||
$this->invalidateCache($folder->getUserId(), $folder->getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,7 +39,7 @@ class ShareMapper extends QBMapper {
|
|||
* @throws DoesNotExistException
|
||||
* @throws MultipleObjectsReturnedException
|
||||
*/
|
||||
public function find(int $shareId) {
|
||||
public function find(int $shareId): Entity {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select(Share::$columns)
|
||||
->from('bookmarks_shares')
|
||||
|
|
|
@ -34,7 +34,7 @@ class SharedFolderMapper extends QBMapper {
|
|||
* @param int $shareId
|
||||
* @return Entity[]
|
||||
*/
|
||||
public function findByShare(int $shareId) {
|
||||
public function findByShare(int $shareId): array {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select(SharedFolder::$columns)
|
||||
->from('bookmarks_shared')
|
||||
|
@ -46,9 +46,9 @@ class SharedFolderMapper extends QBMapper {
|
|||
* @param int $folderId
|
||||
* @return Entity[]
|
||||
*/
|
||||
public function findByFolder(int $folderId) {
|
||||
public function findByFolder(int $folderId): array {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select(array_map(function ($c) {
|
||||
$qb->select(array_map(static function ($c) {
|
||||
return 'p.' . $c;
|
||||
}, SharedFolder::$columns))
|
||||
->from('bookmarks_shared', 'p')
|
||||
|
@ -61,11 +61,12 @@ class SharedFolderMapper extends QBMapper {
|
|||
* @param int $folderId
|
||||
* @return Entity[]
|
||||
*/
|
||||
public function findByParentFolder(int $folderId) {
|
||||
public function findByParentFolder(int $folderId): array {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select(SharedFolder::$columns)
|
||||
->from('bookmarks_shared')
|
||||
->where($qb->expr()->eq('parent_folder', $qb->createPositionalParameter($folderId)));
|
||||
->join('p', 'bookmarks_tree', 't', $qb->expr()->eq('t.parent_folder', 'p.id'))
|
||||
->where($qb->expr()->eq('t.id', $qb->createPositionalParameter($folderId)));
|
||||
return $this->findEntities($qb);
|
||||
}
|
||||
|
||||
|
@ -73,9 +74,9 @@ class SharedFolderMapper extends QBMapper {
|
|||
* @param string $userId
|
||||
* @return array|Entity[]
|
||||
*/
|
||||
public function findByOwner(string $userId) {
|
||||
public function findByOwner(string $userId): array {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select(array_map(function ($c) {
|
||||
$qb->select(array_map(static function ($c) {
|
||||
return 'p.' . $c;
|
||||
}, SharedFolder::$columns))
|
||||
->from('bookmarks_shared', 'p')
|
||||
|
@ -89,7 +90,7 @@ class SharedFolderMapper extends QBMapper {
|
|||
* @param string $participant
|
||||
* @return array|Entity[]
|
||||
*/
|
||||
public function findByParticipant(int $type, string $participant) {
|
||||
public function findByParticipant(int $type, string $participant): array {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select(array_map(function ($c) {
|
||||
return 'p.' . $c;
|
||||
|
@ -109,9 +110,9 @@ class SharedFolderMapper extends QBMapper {
|
|||
* @throws DoesNotExistException
|
||||
* @throws MultipleObjectsReturnedException
|
||||
*/
|
||||
public function findByFolderAndParticipant(int $folderId, int $type, string $participant) {
|
||||
public function findByFolderAndParticipant(int $folderId, int $type, string $participant): Entity {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select(array_map(function ($c) {
|
||||
$qb->select(array_map(static function ($c) {
|
||||
return 'p.' . $c;
|
||||
}, SharedFolder::$columns))
|
||||
->from('bookmarks_shared', 'p')
|
||||
|
@ -129,9 +130,9 @@ class SharedFolderMapper extends QBMapper {
|
|||
* @throws DoesNotExistException
|
||||
* @throws MultipleObjectsReturnedException
|
||||
*/
|
||||
public function findByFolderAndUser(int $folderId, string $userId) {
|
||||
public function findByFolderAndUser(int $folderId, string $userId): Entity {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select(array_map(function ($c) {
|
||||
$qb->select(array_map(static function ($c) {
|
||||
return 'p.' . $c;
|
||||
}, SharedFolder::$columns))
|
||||
->from('bookmarks_shared', 'p')
|
||||
|
@ -146,9 +147,9 @@ class SharedFolderMapper extends QBMapper {
|
|||
* @param string $userId
|
||||
* @return Entity[]
|
||||
*/
|
||||
public function findByOwnerAndUser(string $owner, string $userId) {
|
||||
public function findByOwnerAndUser(string $owner, string $userId): array {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$$qb->select(array_map(function ($c) {
|
||||
$$qb->select(array_map(static function ($c) {
|
||||
return 'p.' . $c;
|
||||
}, SharedFolder::$columns))
|
||||
->from('bookmarks_shared', 'p')
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace OCA\Bookmarks\Db;
|
||||
|
||||
use Doctrine\DBAL\Driver\Statement;
|
||||
use InvalidArgumentException;
|
||||
use OCP\IDBConnection;
|
||||
|
||||
|
@ -30,7 +31,7 @@ class TagMapper {
|
|||
* @param $userId
|
||||
* @return array
|
||||
*/
|
||||
public function findAllWithCount($userId) {
|
||||
public function findAllWithCount($userId): array {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb
|
||||
->select('t.tag')
|
||||
|
@ -49,7 +50,7 @@ class TagMapper {
|
|||
* @param $userId
|
||||
* @return array
|
||||
*/
|
||||
public function findAll($userId) {
|
||||
public function findAll($userId): array {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb
|
||||
->select('t.tag')
|
||||
|
@ -64,7 +65,7 @@ class TagMapper {
|
|||
* @param int $bookmarkId
|
||||
* @return array
|
||||
*/
|
||||
public function findByBookmark(int $bookmarkId) {
|
||||
public function findByBookmark(int $bookmarkId): array {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select('tag');
|
||||
|
||||
|
@ -78,7 +79,7 @@ class TagMapper {
|
|||
/**
|
||||
* @param $userId
|
||||
* @param string $tag
|
||||
* @return \Doctrine\DBAL\Driver\Statement|int
|
||||
* @return Statement|int
|
||||
*/
|
||||
public function delete($userId, string $tag) {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
|
@ -92,7 +93,7 @@ class TagMapper {
|
|||
|
||||
/**
|
||||
* @param $userId
|
||||
* @return \Doctrine\DBAL\Driver\Statement|int
|
||||
* @return Statement|int
|
||||
*/
|
||||
public function deleteAll(int $userId) {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
|
@ -107,7 +108,7 @@ class TagMapper {
|
|||
* @param $tags
|
||||
* @param int $bookmarkId
|
||||
*/
|
||||
public function addTo($tags, int $bookmarkId) {
|
||||
public function addTo($tags, int $bookmarkId): void {
|
||||
if (is_string($tags)) {
|
||||
$tags = [$tags];
|
||||
} else if (!is_array($tags)) {
|
||||
|
@ -141,7 +142,7 @@ class TagMapper {
|
|||
/**
|
||||
* @param int $bookmarkId
|
||||
*/
|
||||
public function removeAllFrom(int $bookmarkId) {
|
||||
public function removeAllFrom(int $bookmarkId): void {
|
||||
// Remove old tags
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb
|
||||
|
@ -154,7 +155,7 @@ class TagMapper {
|
|||
* @param array $tags
|
||||
* @param int $bookmarkId
|
||||
*/
|
||||
public function setOn(array $tags, int $bookmarkId) {
|
||||
public function setOn(array $tags, int $bookmarkId): void {
|
||||
$this->removeAllFrom($bookmarkId);
|
||||
$this->addTo($tags, $bookmarkId);
|
||||
}
|
||||
|
@ -164,9 +165,8 @@ class TagMapper {
|
|||
* @param $userId UserId
|
||||
* @param string $old Old Tag Name
|
||||
* @param string $new New Tag Name
|
||||
* @return boolean Success of operation
|
||||
*/
|
||||
public function renameTag($userId, string $old, string $new) {
|
||||
public function renameTag($userId, string $old, string $new): void {
|
||||
// Remove about-to-be duplicated tags
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb
|
||||
|
@ -205,6 +205,5 @@ class TagMapper {
|
|||
->andWhere($qb->expr()->in('bookmark_id', array_map([$qb, 'createNamedParameter'], $bookmarks)));
|
||||
$qb->execute();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
<?php
|
||||
|
||||
|
||||
namespace OCA\Bookmarks\Exception;
|
||||
|
||||
class UnsupportedOperation extends Exception {
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
<?php
|
||||
|
||||
|
||||
namespace OCA\Bookmarks;
|
||||
|
||||
|
||||
class QueryParameters {
|
||||
public const CONJ_AND = 'and';
|
||||
public const CONJ_OR = 'or';
|
||||
|
||||
private $limit = 10;
|
||||
private $offset = 0;
|
||||
private $sortBy = null;
|
||||
private $conjunction = self::CONJ_AND;
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getLimit(): int {
|
||||
return $this->limit;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $limit
|
||||
* @return QueryParameters
|
||||
*/
|
||||
public function setLimit(int $limit): QueryParameters {
|
||||
$this->limit = $limit;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getOffset(): int {
|
||||
return $this->offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $offset
|
||||
* @return QueryParameters
|
||||
*/
|
||||
public function setOffset(int $offset): QueryParameters {
|
||||
$this->offset = $offset;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $default
|
||||
* @param array|null $columns
|
||||
* @return string
|
||||
*/
|
||||
public function getSortBy(string $default = null, array $columns = null): string {
|
||||
if (isset($columns) && !in_array($columns, $this->sortBy, true)) {
|
||||
return $default;
|
||||
}
|
||||
if (isset($default) && !isset($this->sortBy)) {
|
||||
return $default;
|
||||
}
|
||||
return $this->sortBy;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sortBy
|
||||
* @return QueryParameters
|
||||
*/
|
||||
public function setSortBy(string $sortBy): QueryParameters {
|
||||
$this->sortBy = $sortBy;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getConjunction(): string {
|
||||
return $this->conjunction;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $conjunction
|
||||
* @return QueryParameters
|
||||
*/
|
||||
public function setConjunction(string $conjunction): QueryParameters {
|
||||
if ($conjunction !== self::CONJ_AND && $conjunction !== self::CONJ_OR) {
|
||||
throw new \InvalidArgumentException("Conjunction value must be 'and' or 'or'");
|
||||
}
|
||||
$this->conjunction = $conjunction;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -6,7 +6,10 @@ namespace OCA\Bookmarks\Tests;
|
|||
use OCA\Bookmarks\Db;
|
||||
use OCA\Bookmarks\Db\Bookmark;
|
||||
use OCA\Bookmarks\Db\Folder;
|
||||
use OCA\Bookmarks\Exception\AlreadyExistsError;
|
||||
use OCA\Bookmarks\Exception\UrlParseError;
|
||||
use OCA\Bookmarks\Exception\UserLimitExceededError;
|
||||
use OCA\Bookmarks\QueryParameters;
|
||||
use OCP\AppFramework\Db\DoesNotExistException;
|
||||
use OCP\AppFramework\Db\Entity;
|
||||
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
|
||||
|
@ -48,17 +51,22 @@ class FolderMapperTest extends TestCase {
|
|||
* @return void
|
||||
* @throws DoesNotExistException
|
||||
* @throws MultipleObjectsReturnedException
|
||||
* @throws \OCA\Bookmarks\Exception\UnsupportedOperation
|
||||
*/
|
||||
public function testInsertAndFind() {
|
||||
public function testInsertAndFind(): void {
|
||||
$folder = new Db\Folder();
|
||||
$folder->setTitle('foobar');
|
||||
$folder->setParentFolder(-1);
|
||||
$folder->setUserId($this->userId);
|
||||
$insertedFolder = $this->folderMapper->insert($folder);
|
||||
|
||||
$rootFolder = $this->folderMapper->findRootFolder($this->userId);
|
||||
$this->folderMapper->move($insertedFolder->getId(), $rootFolder->getId());
|
||||
|
||||
$foundEntity = $this->folderMapper->find($insertedFolder->getId());
|
||||
$this->assertSame($foundEntity->getTitle(), $foundEntity->getTitle());
|
||||
$this->assertSame($foundEntity->getParentFolder(), $foundEntity->getParentFolder());
|
||||
return $insertedFolder;
|
||||
|
||||
$parent = $this->folderMapper->findParentOfFolder($insertedFolder->getId());
|
||||
$this->assertSame($parent->getId(), $rootFolder->getId());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -68,7 +76,7 @@ class FolderMapperTest extends TestCase {
|
|||
* @throws DoesNotExistException
|
||||
* @throws MultipleObjectsReturnedException
|
||||
*/
|
||||
public function testUpdate(Entity $folder) {
|
||||
public function testUpdate(Entity $folder): void {
|
||||
$folder->setTitle('barbla');
|
||||
$this->folderMapper->update($folder);
|
||||
$foundEntity = $this->folderMapper->find($folder->getId());
|
||||
|
@ -83,13 +91,16 @@ class FolderMapperTest extends TestCase {
|
|||
* @return void
|
||||
* @throws DoesNotExistException
|
||||
* @throws MultipleObjectsReturnedException
|
||||
* @throws UrlParseError
|
||||
* @throws AlreadyExistsError
|
||||
* @throws UserLimitExceededError
|
||||
*/
|
||||
public function testAddBookmarks(Bookmark $bookmark, Folder $folder) {
|
||||
public function testAddBookmarks(Bookmark $bookmark, Folder $folder): void {
|
||||
$bookmark->setUserId($this->userId);
|
||||
$insertedBookmark = $this->bookmarkMapper->insertOrUpdate($bookmark);
|
||||
$this->folderMapper->addToFolders($insertedBookmark->getId(), [$folder->getId()]);
|
||||
$bookmarks = $this->bookmarkMapper->findByFolder($folder->getId());
|
||||
$this->assertContains($insertedBookmark->getId(), array_map(function($bookmark) {
|
||||
$bookmarks = $this->bookmarkMapper->findByFolder($folder->getId(), new QueryParameters());
|
||||
$this->assertContains($insertedBookmark->getId(), array_map(static function(Bookmark $bookmark) {
|
||||
return $bookmark->getId();
|
||||
}, $bookmarks));
|
||||
}
|
||||
|
@ -103,13 +114,15 @@ class FolderMapperTest extends TestCase {
|
|||
* @throws DoesNotExistException
|
||||
* @throws MultipleObjectsReturnedException
|
||||
* @throws UrlParseError
|
||||
* @throws AlreadyExistsError
|
||||
* @throws UserLimitExceededError
|
||||
*/
|
||||
public function testRemoveBookmarks(Bookmark $bookmark, Folder $folder) {
|
||||
public function testRemoveBookmarks(Bookmark $bookmark, Folder $folder): void {
|
||||
$bookmark->setUserId($this->userId);
|
||||
$insertedBookmark = $this->bookmarkMapper->insertOrUpdate($bookmark);
|
||||
$this->folderMapper->removeFromFolders($insertedBookmark->getId(), [$folder->getId()]);
|
||||
$bookmarks = $this->bookmarkMapper->findByFolder($folder->getId());
|
||||
$this->assertNotContains($insertedBookmark->getId(), array_map(function($bookmark) {
|
||||
$bookmarks = $this->bookmarkMapper->findByFolder($folder->getId(), new QueryParameters());
|
||||
$this->assertNotContains($insertedBookmark->getId(), array_map(static function($bookmark) {
|
||||
return $bookmark->getId();
|
||||
}, $bookmarks));
|
||||
}
|
||||
|
@ -121,7 +134,7 @@ class FolderMapperTest extends TestCase {
|
|||
* @throws DoesNotExistException
|
||||
* @throws MultipleObjectsReturnedException
|
||||
*/
|
||||
public function testDelete(Entity $folder) {
|
||||
public function testDelete(Entity $folder): void {
|
||||
$this->folderMapper->delete($folder);
|
||||
$this->expectException(DoesNotExistException::class);
|
||||
$this->folderMapper->find($folder->getId());
|
||||
|
@ -130,8 +143,8 @@ class FolderMapperTest extends TestCase {
|
|||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function singleBookmarksProvider() {
|
||||
return array_map(function($props) {
|
||||
public function singleBookmarksProvider(): array {
|
||||
return array_map(static function($props) {
|
||||
return [Db\Bookmark::fromArray($props)];
|
||||
}, [
|
||||
'Simple URL with title and description' => ['url' => 'https://google.com/', 'title' => 'Google', 'description' => 'Search engine'],
|
||||
|
|
Loading…
Reference in New Issue