diff options
Diffstat (limited to 'src/Wallabag/CoreBundle/Repository/EntryRepository.php')
-rw-r--r-- | src/Wallabag/CoreBundle/Repository/EntryRepository.php | 138 |
1 files changed, 122 insertions, 16 deletions
diff --git a/src/Wallabag/CoreBundle/Repository/EntryRepository.php b/src/Wallabag/CoreBundle/Repository/EntryRepository.php index 83379998..bfd07937 100644 --- a/src/Wallabag/CoreBundle/Repository/EntryRepository.php +++ b/src/Wallabag/CoreBundle/Repository/EntryRepository.php | |||
@@ -3,11 +3,13 @@ | |||
3 | namespace Wallabag\CoreBundle\Repository; | 3 | namespace Wallabag\CoreBundle\Repository; |
4 | 4 | ||
5 | use Doctrine\ORM\EntityRepository; | 5 | use Doctrine\ORM\EntityRepository; |
6 | use Doctrine\ORM\NoResultException; | ||
6 | use Doctrine\ORM\QueryBuilder; | 7 | use Doctrine\ORM\QueryBuilder; |
7 | use Pagerfanta\Adapter\DoctrineORMAdapter; | 8 | use Pagerfanta\Adapter\DoctrineORMAdapter; |
8 | use Pagerfanta\Pagerfanta; | 9 | use Pagerfanta\Pagerfanta; |
9 | use Wallabag\CoreBundle\Entity\Entry; | 10 | use Wallabag\CoreBundle\Entity\Entry; |
10 | use Wallabag\CoreBundle\Entity\Tag; | 11 | use Wallabag\CoreBundle\Entity\Tag; |
12 | use Wallabag\CoreBundle\Helper\UrlHasher; | ||
11 | 13 | ||
12 | class EntryRepository extends EntityRepository | 14 | class EntryRepository extends EntityRepository |
13 | { | 15 | { |
@@ -50,7 +52,7 @@ class EntryRepository extends EntityRepository | |||
50 | public function getBuilderForArchiveByUser($userId) | 52 | public function getBuilderForArchiveByUser($userId) |
51 | { | 53 | { |
52 | return $this | 54 | return $this |
53 | ->getSortedQueryBuilderByUser($userId) | 55 | ->getSortedQueryBuilderByUser($userId, 'archivedAt', 'desc') |
54 | ->andWhere('e.isArchived = true') | 56 | ->andWhere('e.isArchived = true') |
55 | ; | 57 | ; |
56 | } | 58 | } |
@@ -110,8 +112,7 @@ class EntryRepository extends EntityRepository | |||
110 | */ | 112 | */ |
111 | public function getBuilderForUntaggedByUser($userId) | 113 | public function getBuilderForUntaggedByUser($userId) |
112 | { | 114 | { |
113 | return $this | 115 | return $this->sortQueryBuilder($this->getRawBuilderForUntaggedByUser($userId)); |
114 | ->sortQueryBuilder($this->getRawBuilderForUntaggedByUser($userId)); | ||
115 | } | 116 | } |
116 | 117 | ||
117 | /** | 118 | /** |
@@ -129,6 +130,21 @@ class EntryRepository extends EntityRepository | |||
129 | } | 130 | } |
130 | 131 | ||
131 | /** | 132 | /** |
133 | * Retrieve the number of untagged entries for a user. | ||
134 | * | ||
135 | * @param int $userId | ||
136 | * | ||
137 | * @return int | ||
138 | */ | ||
139 | public function countUntaggedEntriesByUser($userId) | ||
140 | { | ||
141 | return (int) $this->getRawBuilderForUntaggedByUser($userId) | ||
142 | ->select('count(e.id)') | ||
143 | ->getQuery() | ||
144 | ->getSingleScalarResult(); | ||
145 | } | ||
146 | |||
147 | /** | ||
132 | * Find Entries. | 148 | * Find Entries. |
133 | * | 149 | * |
134 | * @param int $userId | 150 | * @param int $userId |
@@ -139,15 +155,30 @@ class EntryRepository extends EntityRepository | |||
139 | * @param string $order | 155 | * @param string $order |
140 | * @param int $since | 156 | * @param int $since |
141 | * @param string $tags | 157 | * @param string $tags |
158 | * @param string $detail 'metadata' or 'full'. Include content field if 'full' | ||
159 | * | ||
160 | * @todo Breaking change: replace default detail=full by detail=metadata in a future version | ||
142 | * | 161 | * |
143 | * @return Pagerfanta | 162 | * @return Pagerfanta |
144 | */ | 163 | */ |
145 | public function findEntries($userId, $isArchived = null, $isStarred = null, $isPublic = null, $sort = 'created', $order = 'ASC', $since = 0, $tags = '') | 164 | public function findEntries($userId, $isArchived = null, $isStarred = null, $isPublic = null, $sort = 'created', $order = 'asc', $since = 0, $tags = '', $detail = 'full') |
146 | { | 165 | { |
166 | if (!\in_array(strtolower($detail), ['full', 'metadata'], true)) { | ||
167 | throw new \Exception('Detail "' . $detail . '" parameter is wrong, allowed: full or metadata'); | ||
168 | } | ||
169 | |||
147 | $qb = $this->createQueryBuilder('e') | 170 | $qb = $this->createQueryBuilder('e') |
148 | ->leftJoin('e.tags', 't') | 171 | ->leftJoin('e.tags', 't') |
149 | ->where('e.user = :userId')->setParameter('userId', $userId); | 172 | ->where('e.user = :userId')->setParameter('userId', $userId); |
150 | 173 | ||
174 | if ('metadata' === $detail) { | ||
175 | $fieldNames = $this->getClassMetadata()->getFieldNames(); | ||
176 | $fields = array_filter($fieldNames, function ($k) { | ||
177 | return 'content' !== $k; | ||
178 | }); | ||
179 | $qb->select(sprintf('partial e.{%s}', implode(',', $fields))); | ||
180 | } | ||
181 | |||
151 | if (null !== $isArchived) { | 182 | if (null !== $isArchived) { |
152 | $qb->andWhere('e.isArchived = :isArchived')->setParameter('isArchived', (bool) $isArchived); | 183 | $qb->andWhere('e.isArchived = :isArchived')->setParameter('isArchived', (bool) $isArchived); |
153 | } | 184 | } |
@@ -185,10 +216,16 @@ class EntryRepository extends EntityRepository | |||
185 | } | 216 | } |
186 | } | 217 | } |
187 | 218 | ||
219 | if (!\in_array(strtolower($order), ['asc', 'desc'], true)) { | ||
220 | throw new \Exception('Order "' . $order . '" parameter is wrong, allowed: asc or desc'); | ||
221 | } | ||
222 | |||
188 | if ('created' === $sort) { | 223 | if ('created' === $sort) { |
189 | $qb->orderBy('e.id', $order); | 224 | $qb->orderBy('e.id', $order); |
190 | } elseif ('updated' === $sort) { | 225 | } elseif ('updated' === $sort) { |
191 | $qb->orderBy('e.updatedAt', $order); | 226 | $qb->orderBy('e.updatedAt', $order); |
227 | } elseif ('archived' === $sort) { | ||
228 | $qb->orderBy('e.archivedAt', $order); | ||
192 | } | 229 | } |
193 | 230 | ||
194 | $pagerAdapter = new DoctrineORMAdapter($qb, true, false); | 231 | $pagerAdapter = new DoctrineORMAdapter($qb, true, false); |
@@ -269,7 +306,6 @@ class EntryRepository extends EntityRepository | |||
269 | * DELETE et FROM entry_tag et WHERE et.entry_id IN ( SELECT e.id FROM entry e WHERE e.user_id = :userId ) AND et.tag_id = :tagId | 306 | * DELETE et FROM entry_tag et WHERE et.entry_id IN ( SELECT e.id FROM entry e WHERE e.user_id = :userId ) AND et.tag_id = :tagId |
270 | * | 307 | * |
271 | * @param int $userId | 308 | * @param int $userId |
272 | * @param Tag $tag | ||
273 | */ | 309 | */ |
274 | public function removeTag($userId, Tag $tag) | 310 | public function removeTag($userId, Tag $tag) |
275 | { | 311 | { |
@@ -320,15 +356,44 @@ class EntryRepository extends EntityRepository | |||
320 | * Find an entry by its url and its owner. | 356 | * Find an entry by its url and its owner. |
321 | * If it exists, return the entry otherwise return false. | 357 | * If it exists, return the entry otherwise return false. |
322 | * | 358 | * |
323 | * @param $url | 359 | * @param string $url |
324 | * @param $userId | 360 | * @param int $userId |
325 | * | 361 | * |
326 | * @return Entry|bool | 362 | * @return Entry|false |
327 | */ | 363 | */ |
328 | public function findByUrlAndUserId($url, $userId) | 364 | public function findByUrlAndUserId($url, $userId) |
329 | { | 365 | { |
366 | return $this->findByHashedUrlAndUserId( | ||
367 | UrlHasher::hashUrl($url), | ||
368 | $userId | ||
369 | ); | ||
370 | } | ||
371 | |||
372 | /** | ||
373 | * Find an entry by its hashed url and its owner. | ||
374 | * If it exists, return the entry otherwise return false. | ||
375 | * | ||
376 | * @param string $hashedUrl Url hashed using sha1 | ||
377 | * @param int $userId | ||
378 | * | ||
379 | * @return Entry|false | ||
380 | */ | ||
381 | public function findByHashedUrlAndUserId($hashedUrl, $userId) | ||
382 | { | ||
383 | // try first using hashed_url (to use the database index) | ||
330 | $res = $this->createQueryBuilder('e') | 384 | $res = $this->createQueryBuilder('e') |
331 | ->where('e.url = :url')->setParameter('url', urldecode($url)) | 385 | ->where('e.hashedUrl = :hashed_url')->setParameter('hashed_url', $hashedUrl) |
386 | ->andWhere('e.user = :user_id')->setParameter('user_id', $userId) | ||
387 | ->getQuery() | ||
388 | ->getResult(); | ||
389 | |||
390 | if (\count($res)) { | ||
391 | return current($res); | ||
392 | } | ||
393 | |||
394 | // then try using hashed_given_url (to use the database index) | ||
395 | $res = $this->createQueryBuilder('e') | ||
396 | ->where('e.hashedGivenUrl = :hashed_given_url')->setParameter('hashed_given_url', $hashedUrl) | ||
332 | ->andWhere('e.user = :user_id')->setParameter('user_id', $userId) | 397 | ->andWhere('e.user = :user_id')->setParameter('user_id', $userId) |
333 | ->getQuery() | 398 | ->getQuery() |
334 | ->getResult(); | 399 | ->getResult(); |
@@ -412,8 +477,8 @@ class EntryRepository extends EntityRepository | |||
412 | /** | 477 | /** |
413 | * Find all entries by url and owner. | 478 | * Find all entries by url and owner. |
414 | * | 479 | * |
415 | * @param $url | 480 | * @param string $url |
416 | * @param $userId | 481 | * @param int $userId |
417 | * | 482 | * |
418 | * @return array | 483 | * @return array |
419 | */ | 484 | */ |
@@ -427,6 +492,49 @@ class EntryRepository extends EntityRepository | |||
427 | } | 492 | } |
428 | 493 | ||
429 | /** | 494 | /** |
495 | * Returns a random entry, filtering by status. | ||
496 | * | ||
497 | * @param int $userId | ||
498 | * @param string $type Can be unread, archive, starred, etc | ||
499 | * | ||
500 | * @throws NoResultException | ||
501 | * | ||
502 | * @return Entry | ||
503 | */ | ||
504 | public function getRandomEntry($userId, $type = '') | ||
505 | { | ||
506 | $qb = $this->getQueryBuilderByUser($userId) | ||
507 | ->select('e.id'); | ||
508 | |||
509 | switch ($type) { | ||
510 | case 'unread': | ||
511 | $qb->andWhere('e.isArchived = false'); | ||
512 | break; | ||
513 | case 'archive': | ||
514 | $qb->andWhere('e.isArchived = true'); | ||
515 | break; | ||
516 | case 'starred': | ||
517 | $qb->andWhere('e.isStarred = true'); | ||
518 | break; | ||
519 | case 'untagged': | ||
520 | $qb->leftJoin('e.tags', 't'); | ||
521 | $qb->andWhere('t.id is null'); | ||
522 | break; | ||
523 | } | ||
524 | |||
525 | $ids = $qb->getQuery()->getArrayResult(); | ||
526 | |||
527 | if (empty($ids)) { | ||
528 | throw new NoResultException(); | ||
529 | } | ||
530 | |||
531 | // random select one in the list | ||
532 | $randomId = $ids[mt_rand(0, \count($ids) - 1)]['id']; | ||
533 | |||
534 | return $this->find($randomId); | ||
535 | } | ||
536 | |||
537 | /** | ||
430 | * Return a query builder to be used by other getBuilderFor* method. | 538 | * Return a query builder to be used by other getBuilderFor* method. |
431 | * | 539 | * |
432 | * @param int $userId | 540 | * @param int $userId |
@@ -456,15 +564,13 @@ class EntryRepository extends EntityRepository | |||
456 | /** | 564 | /** |
457 | * Return the given QueryBuilder with an orderBy() call. | 565 | * Return the given QueryBuilder with an orderBy() call. |
458 | * | 566 | * |
459 | * @param QueryBuilder $qb | 567 | * @param string $sortBy |
460 | * @param string $sortBy | 568 | * @param string $direction |
461 | * @param string $direction | ||
462 | * | 569 | * |
463 | * @return QueryBuilder | 570 | * @return QueryBuilder |
464 | */ | 571 | */ |
465 | private function sortQueryBuilder(QueryBuilder $qb, $sortBy = 'createdAt', $direction = 'desc') | 572 | private function sortQueryBuilder(QueryBuilder $qb, $sortBy = 'createdAt', $direction = 'desc') |
466 | { | 573 | { |
467 | return $qb | 574 | return $qb->orderBy(sprintf('e.%s', $sortBy), $direction); |
468 | ->orderBy(sprintf('e.%s', $sortBy), $direction); | ||
469 | } | 575 | } |
470 | } | 576 | } |