1
0
Fork 0
mirror of https://github.com/wallabag/wallabag.git synced 2025-09-10 18:51:02 +00:00

Merge remote-tracking branch 'origin/master' into 2.4

This commit is contained in:
Jeremy Benoist 2019-01-15 09:41:18 +01:00
commit 5419a8368e
No known key found for this signature in database
GPG key ID: BCA73962457ACC3C
61 changed files with 3813 additions and 1509 deletions

View file

@ -9,6 +9,7 @@ use Nelmio\ApiDocBundle\Annotation\ApiDoc;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Wallabag\CoreBundle\Entity\Entry;
@ -98,24 +99,28 @@ class EntryRestController extends WallabagRestController
$isArchived = (null === $request->query->get('archive')) ? null : (bool) $request->query->get('archive');
$isStarred = (null === $request->query->get('starred')) ? null : (bool) $request->query->get('starred');
$isPublic = (null === $request->query->get('public')) ? null : (bool) $request->query->get('public');
$sort = $request->query->get('sort', 'created');
$order = $request->query->get('order', 'desc');
$sort = strtolower($request->query->get('sort', 'created'));
$order = strtolower($request->query->get('order', 'desc'));
$page = (int) $request->query->get('page', 1);
$perPage = (int) $request->query->get('perPage', 30);
$tags = \is_array($request->query->get('tags')) ? '' : (string) $request->query->get('tags', '');
$since = $request->query->get('since', 0);
/** @var \Pagerfanta\Pagerfanta $pager */
$pager = $this->get('wallabag_core.entry_repository')->findEntries(
$this->getUser()->getId(),
$isArchived,
$isStarred,
$isPublic,
$sort,
$order,
$since,
$tags
);
try {
/** @var \Pagerfanta\Pagerfanta $pager */
$pager = $this->get('wallabag_core.entry_repository')->findEntries(
$this->getUser()->getId(),
$isArchived,
$isStarred,
$isPublic,
$sort,
$order,
$since,
$tags
);
} catch (\Exception $e) {
throw new BadRequestHttpException($e->getMessage());
}
$pager->setMaxPerPage($perPage);
$pager->setCurrentPage($page);

View file

@ -46,12 +46,14 @@ class TagRestController extends WallabagRestController
$this->validateAuthentication();
$label = $request->get('tag', '');
$tag = $this->getDoctrine()->getRepository('WallabagCoreBundle:Tag')->findOneByLabel($label);
$tags = $this->getDoctrine()->getRepository('WallabagCoreBundle:Tag')->findByLabelsAndUser([$label], $this->getUser()->getId());
if (empty($tag)) {
if (empty($tags)) {
throw $this->createNotFoundException('Tag not found');
}
$tag = $tags[0];
$this->getDoctrine()
->getRepository('WallabagCoreBundle:Entry')
->removeTag($this->getUser()->getId(), $tag);
@ -80,15 +82,7 @@ class TagRestController extends WallabagRestController
$tagsLabels = $request->get('tags', '');
$tags = [];
foreach (explode(',', $tagsLabels) as $tagLabel) {
$tagEntity = $this->getDoctrine()->getRepository('WallabagCoreBundle:Tag')->findOneByLabel($tagLabel);
if (!empty($tagEntity)) {
$tags[] = $tagEntity;
}
}
$tags = $this->getDoctrine()->getRepository('WallabagCoreBundle:Tag')->findByLabelsAndUser(explode(',', $tagsLabels), $this->getUser()->getId());
if (empty($tags)) {
throw $this->createNotFoundException('Tags not found');
@ -120,6 +114,12 @@ class TagRestController extends WallabagRestController
{
$this->validateAuthentication();
$tagFromDb = $this->getDoctrine()->getRepository('WallabagCoreBundle:Tag')->findByLabelsAndUser([$tag->getLabel()], $this->getUser()->getId());
if (empty($tagFromDb)) {
throw $this->createNotFoundException('Tag not found');
}
$this->getDoctrine()
->getRepository('WallabagCoreBundle:Entry')
->removeTag($this->getUser()->getId(), $tag);

View file

@ -11,7 +11,7 @@ use Wallabag\UserBundle\Entity\User;
/**
* @ORM\Table("oauth2_clients")
* @ORM\Entity
* @ORM\Entity(repositoryClass="Wallabag\ApiBundle\Repository\ClientRepository")
*/
class Client extends BaseClient
{

View file

@ -0,0 +1,19 @@
<?php
namespace Wallabag\ApiBundle\Repository;
use Doctrine\ORM\EntityRepository;
class ClientRepository extends EntityRepository
{
public function findOneBy(array $criteria, array $orderBy = null)
{
if (!empty($criteria['id'])) {
// cast client id to be an integer to avoid postgres error:
// "invalid input syntax for integer"
$criteria['id'] = (int) $criteria['id'];
}
return parent::findOneBy($criteria, $orderBy);
}
}

View file

@ -58,6 +58,7 @@ class ExportController extends Controller
$method = ucfirst($category);
$methodBuilder = 'getBuilderFor' . $method . 'ByUser';
$repository = $this->get('wallabag_core.entry_repository');
$title = $method;
if ('tag_entries' === $category) {
$tag = $this->get('wallabag_core.tag_repository')->findOneBySlug($request->query->get('tag'));
@ -66,6 +67,8 @@ class ExportController extends Controller
$this->getUser()->getId(),
$tag->getId()
);
$title = 'Tag ' . $tag->getLabel();
} else {
$entries = $repository
->$methodBuilder($this->getUser()->getId())
@ -76,7 +79,7 @@ class ExportController extends Controller
try {
return $this->get('wallabag_core.helper.entries_export')
->setEntries($entries)
->updateTitle($method)
->updateTitle($title)
->updateAuthor($method)
->exportAs($format);
} catch (\InvalidArgumentException $e) {

View file

@ -15,97 +15,112 @@ class EntryFixtures extends Fixture implements DependentFixtureInterface
*/
public function load(ObjectManager $manager)
{
$entry1 = new Entry($this->getReference('admin-user'));
$entry1->setUrl('http://0.0.0.0/entry1');
$entry1->setReadingTime(11);
$entry1->setDomainName('domain.io');
$entry1->setMimetype('text/html');
$entry1->setTitle('test title entry1');
$entry1->setContent('This is my content /o/');
$entry1->setLanguage('en');
$entries = [
'entry1' => [
'user' => 'admin-user',
'url' => 'http://0.0.0.0/entry1',
'reading_time' => 11,
'domain' => 'domain.io',
'mime' => 'text/html',
'title' => 'test title entry1',
'content' => 'This is my content /o/',
'language' => 'en',
'tags' => ['foo-tag', 'baz-tag'],
],
'entry2' => [
'user' => 'admin-user',
'url' => 'http://0.0.0.0/entry2',
'reading_time' => 1,
'domain' => 'domain.io',
'mime' => 'text/html',
'title' => 'test title entry2',
'content' => 'This is my content /o/',
'origin' => 'ftp://oneftp.tld',
'language' => 'fr',
],
'entry3' => [
'user' => 'bob-user',
'url' => 'http://0.0.0.0/entry3',
'reading_time' => 1,
'domain' => 'domain.io',
'mime' => 'text/html',
'title' => 'test title entry3',
'content' => 'This is my content /o/',
'language' => 'en',
'tags' => ['foo-tag', 'bar-tag', 'bob-tag'],
],
'entry4' => [
'user' => 'admin-user',
'url' => 'http://0.0.0.0/entry4',
'reading_time' => 12,
'domain' => 'domain.io',
'mime' => 'text/html',
'title' => 'test title entry4',
'content' => 'This is my content /o/',
'language' => 'en',
'tags' => ['foo-tag', 'bar-tag'],
],
'entry5' => [
'user' => 'admin-user',
'url' => 'http://0.0.0.0/entry5',
'reading_time' => 12,
'domain' => 'domain.io',
'mime' => 'text/html',
'title' => 'test title entry5',
'content' => 'This is my content /o/',
'language' => 'fr',
'starred' => true,
'preview' => 'http://0.0.0.0/image.jpg',
],
'entry6' => [
'user' => 'admin-user',
'url' => 'http://0.0.0.0/entry6',
'reading_time' => 12,
'domain' => 'domain.io',
'mime' => 'text/html',
'title' => 'test title entry6',
'content' => 'This is my content /o/',
'language' => 'de',
'archived' => true,
'tags' => ['bar-tag'],
],
];
$entry1->addTag($this->getReference('foo-tag'));
$entry1->addTag($this->getReference('baz-tag'));
foreach ($entries as $reference => $item) {
$entry = new Entry($this->getReference($item['user']));
$entry->setUrl($item['url']);
$entry->setReadingTime($item['reading_time']);
$entry->setDomainName($item['domain']);
$entry->setMimetype($item['mime']);
$entry->setTitle($item['title']);
$entry->setContent($item['content']);
$entry->setLanguage($item['language']);
$manager->persist($entry1);
if (isset($item['tags'])) {
foreach ($item['tags'] as $tag) {
$entry->addTag($this->getReference($tag));
}
}
$this->addReference('entry1', $entry1);
if (isset($item['origin'])) {
$entry->setOriginUrl($item['origin']);
}
$entry2 = new Entry($this->getReference('admin-user'));
$entry2->setUrl('http://0.0.0.0/entry2');
$entry2->setReadingTime(1);
$entry2->setDomainName('domain.io');
$entry2->setMimetype('text/html');
$entry2->setTitle('test title entry2');
$entry2->setContent('This is my content /o/');
$entry2->setOriginUrl('ftp://oneftp.tld');
$entry2->setLanguage('fr');
if (isset($item['starred'])) {
$entry->setStarred($item['starred']);
}
$manager->persist($entry2);
if (isset($item['archived'])) {
$entry->setArchived($item['archived']);
}
$this->addReference('entry2', $entry2);
if (isset($item['preview'])) {
$entry->setPreviewPicture($item['preview']);
}
$entry3 = new Entry($this->getReference('bob-user'));
$entry3->setUrl('http://0.0.0.0/entry3');
$entry3->setReadingTime(1);
$entry3->setDomainName('domain.io');
$entry3->setMimetype('text/html');
$entry3->setTitle('test title entry3');
$entry3->setContent('This is my content /o/');
$entry3->setLanguage('en');
$entry3->addTag($this->getReference('foo-tag'));
$entry3->addTag($this->getReference('bar-tag'));
$manager->persist($entry3);
$this->addReference('entry3', $entry3);
$entry4 = new Entry($this->getReference('admin-user'));
$entry4->setUrl('http://0.0.0.0/entry4');
$entry4->setReadingTime(12);
$entry4->setDomainName('domain.io');
$entry4->setMimetype('text/html');
$entry4->setTitle('test title entry4');
$entry4->setContent('This is my content /o/');
$entry4->setLanguage('en');
$entry4->addTag($this->getReference('foo-tag'));
$entry4->addTag($this->getReference('bar-tag'));
$manager->persist($entry4);
$this->addReference('entry4', $entry4);
$entry5 = new Entry($this->getReference('admin-user'));
$entry5->setUrl('http://0.0.0.0/entry5');
$entry5->setReadingTime(12);
$entry5->setDomainName('domain.io');
$entry5->setMimetype('text/html');
$entry5->setTitle('test title entry5');
$entry5->setContent('This is my content /o/');
$entry5->setStarred(true);
$entry5->setLanguage('fr');
$entry5->setPreviewPicture('http://0.0.0.0/image.jpg');
$manager->persist($entry5);
$this->addReference('entry5', $entry5);
$entry6 = new Entry($this->getReference('admin-user'));
$entry6->setUrl('http://0.0.0.0/entry6');
$entry6->setReadingTime(12);
$entry6->setDomainName('domain.io');
$entry6->setMimetype('text/html');
$entry6->setTitle('test title entry6');
$entry6->setContent('This is my content /o/');
$entry6->updateArchived(true);
$entry6->setLanguage('de');
$entry6->addTag($this->getReference('bar-tag'));
$manager->persist($entry6);
$this->addReference('entry6', $entry6);
$manager->persist($entry);
$this->addReference($reference, $entry);
}
$manager->flush();
}

View file

@ -13,33 +13,22 @@ class TagFixtures extends Fixture
*/
public function load(ObjectManager $manager)
{
$tag1 = new Tag();
$tag1->setLabel('foo bar');
$tags = [
'foo-bar-tag' => 'foo bar', //tag used for EntryControllerTest
'bar-tag' => 'bar',
'baz-tag' => 'baz', // tag used for ExportControllerTest
'foo-tag' => 'foo',
'bob-tag' => 'bob', // tag used for TagRestControllerTest
];
$manager->persist($tag1);
foreach ($tags as $reference => $label) {
$tag = new Tag();
$tag->setLabel($label);
$this->addReference('foo-bar-tag', $tag1);
$manager->persist($tag);
$tag2 = new Tag();
$tag2->setLabel('bar');
$manager->persist($tag2);
$this->addReference('bar-tag', $tag2);
$tag3 = new Tag();
$tag3->setLabel('baz');
$manager->persist($tag3);
$this->addReference('baz-tag', $tag3);
$tag4 = new Tag();
$tag4->setLabel('foo');
$manager->persist($tag4);
$this->addReference('foo-tag', $tag4);
$this->addReference($reference, $tag);
}
$manager->flush();
}

View file

@ -85,7 +85,7 @@ class EntriesExport
public function updateAuthor($method)
{
if ('entry' !== $method) {
$this->author = $method . ' authors';
$this->author = 'Various authors';
return $this;
}
@ -150,8 +150,6 @@ class EntriesExport
*/
$book->setTitle($this->title);
// Could also be the ISBN number, prefered for published books, or a UUID.
$book->setIdentifier($this->title, EPub::IDENTIFIER_URI);
// Not needed, but included for the example, Language is mandatory, but EPub defaults to "en". Use RFC3066 Language codes, such as "en", "da", "fr" etc.
$book->setLanguage($this->language);
$book->setDescription('Some articles saved on my wallabag');
@ -174,27 +172,49 @@ class EntriesExport
$book->setCoverImage('Cover.png', file_get_contents($this->logoPath), 'image/png');
}
$entryIds = [];
$entryCount = \count($this->entries);
$i = 0;
/*
* Adding actual entries
*/
// set tags as subjects
foreach ($this->entries as $entry) {
++$i;
foreach ($entry->getTags() as $tag) {
$book->setSubject($tag->getLabel());
}
$filename = sha1($entry->getTitle());
// the reader in Kobo Devices doesn't likes special caracters
// in filenames, we limit to A-z/0-9
$filename = preg_replace('/[^A-Za-z0-9\-]/', '', $entry->getTitle());
$publishedBy = $entry->getPublishedBy();
$authors = $this->translator->trans('export.unknown');
if (!empty($publishedBy)) {
$authors = implode(',', $publishedBy);
}
$titlepage = $content_start . '<h1>' . $entry->getTitle() . '</h1>' . $this->getExportInformation('PHPePub') . $bookEnd;
$book->addChapter('Title', 'Title.html', $titlepage, true, EPub::EXTERNAL_REF_ADD);
$titlepage = $content_start .
'<h1>' . $entry->getTitle() . '</h1>' .
'<dl>' .
'<dt>' . $this->translator->trans('entry.view.published_by') . '</dt><dd>' . $authors . '</dd>' .
'<dt>' . $this->translator->trans('entry.metadata.reading_time') . '</dt><dd>' . $this->translator->trans('entry.metadata.reading_time_minutes_short', ['%readingTime%' => $entry->getReadingTime()]) . '</dd>' .
'<dt>' . $this->translator->trans('entry.metadata.added_on') . '</dt><dd>' . $entry->getCreatedAt()->format('Y-m-d') . '</dd>' .
'<dt>' . $this->translator->trans('entry.metadata.address') . '</dt><dd><a href="' . $entry->getUrl() . '">' . $entry->getUrl() . '</a></dd>' .
'</dl>' .
$bookEnd;
$book->addChapter("Entry {$i} of {$entryCount}", "{$filename}_cover.html", $titlepage, true, EPub::EXTERNAL_REF_ADD);
$chapter = $content_start . $entry->getContent() . $bookEnd;
$book->addChapter($entry->getTitle(), htmlspecialchars($filename) . '.html', $chapter, true, EPub::EXTERNAL_REF_ADD);
$entryIds[] = $entry->getId();
$book->addChapter($entry->getTitle(), "{$filename}.html", $chapter, true, EPub::EXTERNAL_REF_ADD);
}
$book->buildTOC();
$book->addChapter('Notices', 'Cover2.html', $content_start . $this->getExportInformation('PHPePub') . $bookEnd);
// Could also be the ISBN number, prefered for published books, or a UUID.
$hash = sha1(sprintf('%s:%s', $this->wallabagUrl, implode(',', $entryIds)));
$book->setIdentifier(sprintf('urn:wallabag:%s', $hash), EPub::IDENTIFIER_URI);
return Response::create(
$book->getBook(),
@ -202,7 +222,7 @@ class EntriesExport
[
'Content-Description' => 'File Transfer',
'Content-type' => 'application/epub+zip',
'Content-Disposition' => 'attachment; filename="' . $this->title . '.epub"',
'Content-Disposition' => 'attachment; filename="' . $this->getSanitizedFilename() . '.epub"',
'Content-Transfer-Encoding' => 'binary',
]
);
@ -244,9 +264,6 @@ class EntriesExport
}
$mobi->setContentProvider($content);
// the browser inside Kindle Devices doesn't likes special caracters either, we limit to A-z/0-9
$this->title = preg_replace('/[^A-Za-z0-9\-]/', '', $this->title);
return Response::create(
$mobi->toString(),
200,
@ -254,7 +271,7 @@ class EntriesExport
'Accept-Ranges' => 'bytes',
'Content-Description' => 'File Transfer',
'Content-type' => 'application/x-mobipocket-ebook',
'Content-Disposition' => 'attachment; filename="' . $this->title . '.mobi"',
'Content-Disposition' => 'attachment; filename="' . $this->getSanitizedFilename() . '.mobi"',
'Content-Transfer-Encoding' => 'binary',
]
);
@ -278,14 +295,6 @@ class EntriesExport
$pdf->SetSubject('Articles via wallabag');
$pdf->SetKeywords('wallabag');
/*
* Front page
*/
$pdf->AddPage();
$intro = '<h1>' . $this->title . '</h1>' . $this->getExportInformation('tcpdf');
$pdf->writeHTMLCell(0, 0, '', '', $intro, 0, 1, 0, true, '', true);
/*
* Adding actual entries
*/
@ -294,6 +303,22 @@ class EntriesExport
$pdf->SetKeywords($tag->getLabel());
}
$publishedBy = $entry->getPublishedBy();
$authors = $this->translator->trans('export.unknown');
if (!empty($publishedBy)) {
$authors = implode(',', $publishedBy);
}
$pdf->addPage();
$html = '<h1>' . $entry->getTitle() . '</h1>' .
'<dl>' .
'<dt>' . $this->translator->trans('entry.view.published_by') . '</dt><dd>' . $authors . '</dd>' .
'<dt>' . $this->translator->trans('entry.metadata.reading_time') . '</dt><dd>' . $this->translator->trans('entry.metadata.reading_time_minutes_short', ['%readingTime%' => $entry->getReadingTime()]) . '</dd>' .
'<dt>' . $this->translator->trans('entry.metadata.added_on') . '</dt><dd>' . $entry->getCreatedAt()->format('Y-m-d') . '</dd>' .
'<dt>' . $this->translator->trans('entry.metadata.address') . '</dt><dd><a href="' . $entry->getUrl() . '">' . $entry->getUrl() . '</a></dd>' .
'</dl>';
$pdf->writeHTMLCell(0, 0, '', '', $html, 0, 1, 0, true, '', true);
$pdf->AddPage();
$html = '<h1>' . $entry->getTitle() . '</h1>';
$html .= $entry->getContent();
@ -301,6 +326,14 @@ class EntriesExport
$pdf->writeHTMLCell(0, 0, '', '', $html, 0, 1, 0, true, '', true);
}
/*
* Last page
*/
$pdf->AddPage();
$html = $this->getExportInformation('tcpdf');
$pdf->writeHTMLCell(0, 0, '', '', $html, 0, 1, 0, true, '', true);
// set image scale factor
$pdf->setImageScale(PDF_IMAGE_SCALE_RATIO);
@ -310,7 +343,7 @@ class EntriesExport
[
'Content-Description' => 'File Transfer',
'Content-type' => 'application/pdf',
'Content-Disposition' => 'attachment; filename="' . $this->title . '.pdf"',
'Content-Disposition' => 'attachment; filename="' . $this->getSanitizedFilename() . '.pdf"',
'Content-Transfer-Encoding' => 'binary',
]
);
@ -356,7 +389,7 @@ class EntriesExport
200,
[
'Content-type' => 'application/csv',
'Content-Disposition' => 'attachment; filename="' . $this->title . '.csv"',
'Content-Disposition' => 'attachment; filename="' . $this->getSanitizedFilename() . '.csv"',
'Content-Transfer-Encoding' => 'UTF-8',
]
);
@ -374,7 +407,7 @@ class EntriesExport
200,
[
'Content-type' => 'application/json',
'Content-Disposition' => 'attachment; filename="' . $this->title . '.json"',
'Content-Disposition' => 'attachment; filename="' . $this->getSanitizedFilename() . '.json"',
'Content-Transfer-Encoding' => 'UTF-8',
]
);
@ -392,7 +425,7 @@ class EntriesExport
200,
[
'Content-type' => 'application/xml',
'Content-Disposition' => 'attachment; filename="' . $this->title . '.xml"',
'Content-Disposition' => 'attachment; filename="' . $this->getSanitizedFilename() . '.xml"',
'Content-Transfer-Encoding' => 'UTF-8',
]
);
@ -418,7 +451,7 @@ class EntriesExport
200,
[
'Content-type' => 'text/plain',
'Content-Disposition' => 'attachment; filename="' . $this->title . '.txt"',
'Content-Disposition' => 'attachment; filename="' . $this->getSanitizedFilename() . '.txt"',
'Content-Transfer-Encoding' => 'UTF-8',
]
);
@ -461,4 +494,15 @@ class EntriesExport
return str_replace('%IMAGE%', '', $info);
}
/**
* Return a sanitized version of the title by applying translit iconv
* and removing non alphanumeric characters, - and space.
*
* @return string Sanitized filename
*/
private function getSanitizedFilename()
{
return preg_replace('/[^A-Za-z0-9\- \']/', '', iconv('utf-8', 'us-ascii//TRANSLIT', $this->title));
}
}

View file

@ -142,7 +142,7 @@ class EntryRepository extends EntityRepository
*
* @return Pagerfanta
*/
public function findEntries($userId, $isArchived = null, $isStarred = null, $isPublic = null, $sort = 'created', $order = 'ASC', $since = 0, $tags = '')
public function findEntries($userId, $isArchived = null, $isStarred = null, $isPublic = null, $sort = 'created', $order = 'asc', $since = 0, $tags = '')
{
$qb = $this->createQueryBuilder('e')
->leftJoin('e.tags', 't')
@ -185,6 +185,10 @@ class EntryRepository extends EntityRepository
}
}
if (!\in_array(strtolower($order), ['asc', 'desc'], true)) {
throw new \Exception('Order "' . $order . '" parameter is wrong, allowed: asc or desc');
}
if ('created' === $sort) {
$qb->orderBy('e.id', $order);
} elseif ('updated' === $sort) {

View file

@ -3,6 +3,7 @@
namespace Wallabag\CoreBundle\Repository;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\QueryBuilder;
use Wallabag\CoreBundle\Entity\Tag;
class TagRepository extends EntityRepository
@ -45,12 +46,8 @@ class TagRepository extends EntityRepository
*/
public function findAllTags($userId)
{
$ids = $this->createQueryBuilder('t')
$ids = $this->getQueryBuilderByUser($userId)
->select('t.id')
->leftJoin('t.entries', 'e')
->where('e.user = :userId')->setParameter('userId', $userId)
->groupBy('t.id')
->orderBy('t.slug')
->getQuery()
->getArrayResult();
@ -71,18 +68,30 @@ class TagRepository extends EntityRepository
*/
public function findAllFlatTagsWithNbEntries($userId)
{
return $this->createQueryBuilder('t')
return $this->getQueryBuilderByUser($userId)
->select('t.id, t.label, t.slug, count(e.id) as nbEntries')
->distinct(true)
->leftJoin('t.entries', 'e')
->where('e.user = :userId')
->groupBy('t.id')
->orderBy('t.slug')
->setParameter('userId', $userId)
->getQuery()
->getArrayResult();
}
public function findByLabelsAndUser($labels, $userId)
{
$qb = $this->getQueryBuilderByUser($userId)
->select('t.id');
$ids = $qb->andWhere($qb->expr()->in('t.label', $labels))
->getQuery()
->getArrayResult();
$tags = [];
foreach ($ids as $id) {
$tags[] = $this->find($id);
}
return $tags;
}
/**
* Used only in test case to get a tag for our entry.
*
@ -101,13 +110,9 @@ class TagRepository extends EntityRepository
public function findForArchivedArticlesByUser($userId)
{
$ids = $this->createQueryBuilder('t')
$ids = $this->getQueryBuilderByUser($userId)
->select('t.id')
->leftJoin('t.entries', 'e')
->where('e.user = :userId')->setParameter('userId', $userId)
->andWhere('e.isArchived = true')
->groupBy('t.id')
->orderBy('t.slug')
->getQuery()
->getArrayResult();
@ -118,4 +123,20 @@ class TagRepository extends EntityRepository
return $tags;
}
/**
* Retrieve a sorted list of tags used by a user.
*
* @param int $userId
*
* @return QueryBuilder
*/
private function getQueryBuilderByUser($userId)
{
return $this->createQueryBuilder('t')
->leftJoin('t.entries', 'e')
->where('e.user = :userId')->setParameter('userId', $userId)
->groupBy('t.id')
->orderBy('t.slug');
}
}

View file

@ -253,6 +253,11 @@ entry:
confirm:
# delete: "Are you sure you want to remove that article?"
# delete_tag: "Are you sure you want to remove that tag from that article?"
metadata:
# reading_time: "Estimated reading time"
# reading_time_minutes_short: "%readingTime% min"
# address: "Address"
# added_on: "Added on"
about:
page_title: 'Om'
@ -404,6 +409,7 @@ tag:
# export:
# footer_template: '<div style="text-align:center;"><p>Produced by wallabag with %method%</p><p>Please open <a href="https://github.com/wallabag/wallabag/issues">an issue</a> if you have trouble with the display of this E-Book on your device.</p></div>'
# unknown: 'Unknown'
import:
# page_title: 'Import'

View file

@ -253,6 +253,11 @@ entry:
confirm:
delete: 'Bist du sicher, dass du diesen Artikel löschen möchtest?'
delete_tag: 'Bist du sicher, dass du diesen Tag vom Artikel entfernen möchtest?'
metadata:
# reading_time: "Estimated reading time"
# reading_time_minutes_short: "%readingTime% min"
# address: "Address"
# added_on: "Added on"
about:
page_title: 'Über'
@ -404,6 +409,7 @@ tag:
export:
footer_template: '<div style="text-align:center;"><p>Generiert von wallabag mit Hilfe von %method%</p><p>Bitte öffne <a href="https://github.com/wallabag/wallabag/issues">ein Ticket</a> wenn du ein Problem mit der Darstellung von diesem E-Book auf deinem Gerät hast.</p></div>'
# unknown: 'Unknown'
import:
page_title: 'Importieren'

View file

@ -253,6 +253,11 @@ entry:
confirm:
delete: "Are you sure you want to remove that article?"
delete_tag: "Are you sure you want to remove that tag from that article?"
metadata:
reading_time: "Estimated reading time"
reading_time_minutes_short: "%readingTime% min"
address: "Address"
added_on: "Added on"
about:
page_title: 'About'
@ -404,6 +409,7 @@ tag:
export:
footer_template: '<div style="text-align:center;"><p>Produced by wallabag with %method%</p><p>Please open <a href="https://github.com/wallabag/wallabag/issues">an issue</a> if you have trouble with the display of this E-Book on your device.</p></div>'
unknown: 'Unknown'
import:
page_title: 'Import'

View file

@ -253,6 +253,11 @@ entry:
confirm:
# delete: "Are you sure you want to remove that article?"
# delete_tag: "Are you sure you want to remove that tag from that article?"
metadata:
# reading_time: "Estimated reading time"
# reading_time_minutes_short: "%readingTime% min"
# address: "Address"
# added_on: "Added on"
about:
page_title: 'Acerca de'
@ -404,6 +409,7 @@ tag:
# export:
# footer_template: '<div style="text-align:center;"><p>Produced by wallabag with %method%</p><p>Please open <a href="https://github.com/wallabag/wallabag/issues">an issue</a> if you have trouble with the display of this E-Book on your device.</p></div>'
# unknown: 'Unknown'
import:
page_title: 'Importar'

View file

@ -253,6 +253,11 @@ entry:
confirm:
# delete: "Are you sure you want to remove that article?"
# delete_tag: "Are you sure you want to remove that tag from that article?"
metadata:
# reading_time: "Estimated reading time"
# reading_time_minutes_short: "%readingTime% min"
# address: "Address"
# added_on: "Added on"
about:
page_title: 'درباره'
@ -404,6 +409,7 @@ tag:
# export:
# footer_template: '<div style="text-align:center;"><p>Produced by wallabag with %method%</p><p>Please open <a href="https://github.com/wallabag/wallabag/issues">an issue</a> if you have trouble with the display of this E-Book on your device.</p></div>'
# unknown: 'Unknown'
import:
page_title: 'درون‌ریزی'

View file

@ -253,6 +253,11 @@ entry:
confirm:
delete: "Voulez-vous vraiment supprimer cet article ?"
delete_tag: "Voulez-vous vraiment supprimer ce tag de cet article ?"
metadata:
reading_time: "Durée de lecture estimée"
reading_time_minutes_short: "%readingTime% min"
address: "Adresse"
added_on: "Ajouté le"
about:
page_title: "À propos"
@ -404,6 +409,7 @@ tag:
export:
footer_template: '<div style="text-align:center;"><p>Généré par wallabag with %method%</p><p>Merci d''ouvrir <a href="https://github.com/wallabag/wallabag/issues">un ticket</a> si vous rencontrez des soucis d''affichage avec ce document sur votre support.</p></div>'
unknown: 'Inconnu'
import:
page_title: "Importer"

View file

@ -253,6 +253,11 @@ entry:
confirm:
delete: "Vuoi veramente rimuovere quell'articolo?"
delete_tag: "Vuoi veramente rimuovere quell'etichetta da quell'articolo?"
metadata:
# reading_time: "Estimated reading time"
# reading_time_minutes_short: "%readingTime% min"
# address: "Address"
# added_on: "Added on"
about:
page_title: 'A proposito'
@ -404,6 +409,7 @@ tag:
# export:
# footer_template: '<div style="text-align:center;"><p>Produced by wallabag with %method%</p><p>Please open <a href="https://github.com/wallabag/wallabag/issues">an issue</a> if you have trouble with the display of this E-Book on your device.</p></div>'
# unknown: 'Unknown'
import:
page_title: 'Importa'

View file

@ -253,6 +253,11 @@ entry:
confirm:
delete: "Sètz segur de voler suprimir aqueste article?"
delete_tag: "Sètz segur de voler levar aquesta etiqueta de l'article?"
metadata:
# reading_time: "Estimated reading time"
# reading_time_minutes_short: "%readingTime% min"
# address: "Address"
# added_on: "Added on"
about:
page_title: 'A prepaus'
@ -404,6 +409,7 @@ tag:
export:
footer_template: '<div style="text-align:center;"><p>Produch per wallabag amb %method%</p><p>Mercés de dobrir <a href="https://github.com/wallabag/wallabag/issues">una sollicitacion</a> savètz de problèmas amb lafichatge daqueste E-Book sus vòstre periferic.</p></div>'
# unknown: 'Unknown'
import:
page_title: 'Importar'

View file

@ -253,6 +253,11 @@ entry:
confirm:
delete: "Czy jesteś pewien, że chcesz usunąć ten artykuł?"
delete_tag: "Czy jesteś pewien, że chcesz usunąć ten tag, z tego artykułu?"
metadata:
# reading_time: "Estimated reading time"
# reading_time_minutes_short: "%readingTime% min"
# address: "Address"
# added_on: "Added on"
about:
page_title: 'O nas'
@ -403,7 +408,8 @@ tag:
placeholder: 'Możesz zaktualizować nazwę taga.'
export:
footer_template: '<div style="text-align:center;"><p>Stworzone przez wallabag z %method%</p><p>Proszę zgłoś <a href="https://github.com/wallabag/wallabag/issues">sprawę</a>, jeżeli masz problem z wyświetleniem tego e-booka na swoim urządzeniu.</p></div>'
footer_template: '<div style="text-align:center;"><p>Stworzone przez wallabag z %method%</p><p>Proszę zgłoś <a href="https://github.com/wallabag/wallabag/issues">sprawę</a>, jeżeli masz problem z wyświetleniem tego e-booka na swoim urządzeniu.</p></div>'
# unknown: 'Unknown'
import:
page_title: 'Import'

View file

@ -253,6 +253,11 @@ entry:
confirm:
# delete: "Are you sure you want to remove that article?"
# delete_tag: "Are you sure you want to remove that tag from that article?"
metadata:
# reading_time: "Estimated reading time"
# reading_time_minutes_short: "%readingTime% min"
# address: "Address"
# added_on: "Added on"
about:
page_title: 'Sobre'
@ -404,6 +409,7 @@ tag:
# export:
# footer_template: '<div style="text-align:center;"><p>Produced by wallabag with %method%</p><p>Please open <a href="https://github.com/wallabag/wallabag/issues">an issue</a> if you have trouble with the display of this E-Book on your device.</p></div>'
# unknown: 'Unknown'
import:
page_title: 'Importar'

View file

@ -253,6 +253,11 @@ entry:
confirm:
# delete: "Are you sure you want to remove that article?"
# delete_tag: "Are you sure you want to remove that tag from that article?"
metadata:
# reading_time: "Estimated reading time"
# reading_time_minutes_short: "%readingTime% min"
# address: "Address"
# added_on: "Added on"
about:
page_title: 'Despre'
@ -404,6 +409,7 @@ tag:
# export:
# footer_template: '<div style="text-align:center;"><p>Produced by wallabag with %method%</p><p>Please open <a href="https://github.com/wallabag/wallabag/issues">an issue</a> if you have trouble with the display of this E-Book on your device.</p></div>'
# unknown: 'Unknown'
import:
# page_title: 'Import'

View file

@ -241,6 +241,11 @@ entry:
save_label: 'Сохранить'
public:
shared_by_wallabag: "Запись была опубликована <a href='%wallabag_instance%'>wallabag</a>"
metadata:
# reading_time: "Estimated reading time"
# reading_time_minutes_short: "%readingTime% min"
# address: "Address"
# added_on: "Added on"
about:
page_title: 'О'
@ -390,6 +395,10 @@ tag:
rename:
# placeholder: 'You can update tag name.'
# export:
# footer_template: '<div style="text-align:center;"><p>Produced by wallabag with %method%</p><p>Please open <a href="https://github.com/wallabag/wallabag/issues">an issue</a> if you have trouble with the display of this E-Book on your device.</p></div>'
# unknown: 'Unknown'
import:
page_title: 'Импорт'
page_description: 'Добро пожаловать в импортер wallabag. Выберите сервис, из которого вы хотите перенести данные.'

View file

@ -251,6 +251,11 @@ entry:
confirm:
delete: "คุณแน่ใจหรือไม่ว่าคุณต้องการลบบทความนี้?"
delete_tag: "คุณแน่ใจหรือไม่ว่าคุณต้องการลบแท็กจากบทความนี้?"
metadata:
# reading_time: "Estimated reading time"
# reading_time_minutes_short: "%readingTime% min"
# address: "Address"
# added_on: "Added on"
about:
page_title: 'เกี่ยวกับ'
@ -402,6 +407,7 @@ tag:
export:
footer_template: '<div style="text-align:center;"><p>ผลิตโดย wallabag กับ %method%</p><p>ให้ทำการเปิด <a href="https://github.com/wallabag/wallabag/issues">ฉบับนี้</a> ถ้าคุณมีข้อบกพร่องif you have trouble with the display of this E-Book on your device.</p></div>'
# unknown: 'Unknown'
import:
page_title: 'นำข้อมูลเช้า'

View file

@ -251,6 +251,11 @@ entry:
confirm:
# delete: "Are you sure you want to remove that article?"
# delete_tag: "Are you sure you want to remove that tag from that article?"
metadata:
# reading_time: "Estimated reading time"
# reading_time_minutes_short: "%readingTime% min"
# address: "Address"
# added_on: "Added on"
about:
page_title: 'Hakkımızda'
@ -402,6 +407,7 @@ tag:
# export:
# footer_template: '<div style="text-align:center;"><p>Produced by wallabag with %method%</p><p>Please open <a href="https://github.com/wallabag/wallabag/issues">an issue</a> if you have trouble with the display of this E-Book on your device.</p></div>'
# unknown: 'Unknown'
import:
page_title: 'İçe Aktar'

View file

@ -99,8 +99,8 @@
{% if craue_setting('export_epub') %}<li class="bold"><a class="waves-effect" href="{{ path('export_entries', { 'category': currentRoute, 'format': 'epub', 'tag' : currentTag }) }}">EPUB</a></li>{% endif %}
{% if craue_setting('export_mobi') %}<li class="bold"><a class="waves-effect" href="{{ path('export_entries', { 'category': currentRoute, 'format': 'mobi', 'tag' : currentTag }) }}">MOBI</a></li>{% endif %}
{% if craue_setting('export_pdf') %}<li class="bold"><a class="waves-effect" href="{{ path('export_entries', { 'category': currentRoute, 'format': 'pdf', 'tag' : currentTag }) }}">PDF</a></li>{% endif %}
{% if craue_setting('export_csv') %}<li class="bold"><a class="waves-effect" href="{{ path('export_entries', { 'category': currentRoute, 'format': 'json', 'tag' : currentTag }) }}">JSON</a></li>{% endif %}
{% if craue_setting('export_json') %}<li class="bold"><a class="waves-effect" href="{{ path('export_entries', { 'category': currentRoute, 'format': 'csv', 'tag' : currentTag }) }}">CSV</a></li>{% endif %}
{% if craue_setting('export_json') %}<li class="bold"><a class="waves-effect" href="{{ path('export_entries', { 'category': currentRoute, 'format': 'json', 'tag' : currentTag }) }}">JSON</a></li>{% endif %}
{% if craue_setting('export_csv') %}<li class="bold"><a class="waves-effect" href="{{ path('export_entries', { 'category': currentRoute, 'format': 'csv', 'tag' : currentTag }) }}">CSV</a></li>{% endif %}
{% if craue_setting('export_txt') %}<li class="bold"><a class="waves-effect" href="{{ path('export_entries', { 'category': currentRoute, 'format': 'txt', 'tag' : currentTag }) }}">TXT</a></li>{% endif %}
{% if craue_setting('export_xml') %}<li class="bold"><a class="waves-effect" href="{{ path('export_entries', { 'category': currentRoute, 'format': 'xml', 'tag' : currentTag }) }}">XML</a></li>{% endif %}
</ul>

View file

@ -96,9 +96,6 @@
</div>
</aside>
</div>
{% if entry.previewPicture is not null %}
<div><img class="preview" src="{{ entry.previewPicture }}" alt="{{ entry.title|e|raw }}" /></div>
{% endif %}
<article>
{{ entry.content | raw }}
</article>

View file

@ -1,10 +1,9 @@
<div class="card-stacked">
<div class="preview">
{% if entry.previewPicture is not null %}
<a href="{{ path('view', { 'id': entry.id }) }}">
<img src="{{ entry.previewPicture }}" />
</a>
{% endif %}
<div class="card-preview">
<a href="{{ path('view', { 'id': entry.id }) }}">
{% set previewClassModifier = entry.previewPicture ? '' : ' preview--default' %}
<span class="preview{{ previewClassModifier }}" style="background-image: url({{ entry.previewPicture | default(asset('wallassets/themes/_global/img/logo-square.svg')) }})"></span>
</a>
</div>
{% include "@WallabagCore/themes/material/Entry/Card/_content.html.twig" with {'entry': entry, 'withTags': true, 'subClass': 'metadata'} only %}
<ul class="tools-list hide-on-small-only">

View file

@ -68,8 +68,8 @@
{% if craue_setting('export_epub') %}<li class="bold"><a class="waves-effect" href="{{ path('export_entries', { 'category': currentRoute, 'format': 'epub', 'tag' : currentTag }) }}">EPUB</a></li>{% endif %}
{% if craue_setting('export_mobi') %}<li class="bold"><a class="waves-effect" href="{{ path('export_entries', { 'category': currentRoute, 'format': 'mobi', 'tag' : currentTag }) }}">MOBI</a></li>{% endif %}
{% if craue_setting('export_pdf') %}<li class="bold"><a class="waves-effect" href="{{ path('export_entries', { 'category': currentRoute, 'format': 'pdf', 'tag' : currentTag }) }}">PDF</a></li>{% endif %}
{% if craue_setting('export_csv') %}<li class="bold"><a class="waves-effect" href="{{ path('export_entries', { 'category': currentRoute, 'format': 'json', 'tag' : currentTag }) }}">JSON</a></li>{% endif %}
{% if craue_setting('export_json') %}<li class="bold"><a class="waves-effect" href="{{ path('export_entries', { 'category': currentRoute, 'format': 'csv', 'tag' : currentTag }) }}">CSV</a></li>{% endif %}
{% if craue_setting('export_json') %}<li class="bold"><a class="waves-effect" href="{{ path('export_entries', { 'category': currentRoute, 'format': 'json', 'tag' : currentTag }) }}">JSON</a></li>{% endif %}
{% if craue_setting('export_csv') %}<li class="bold"><a class="waves-effect" href="{{ path('export_entries', { 'category': currentRoute, 'format': 'csv', 'tag' : currentTag }) }}">CSV</a></li>{% endif %}
{% if craue_setting('export_txt') %}<li class="bold"><a class="waves-effect" href="{{ path('export_entries', { 'category': currentRoute, 'format': 'txt', 'tag' : currentTag }) }}">TXT</a></li>{% endif %}
{% if craue_setting('export_xml') %}<li class="bold"><a class="waves-effect" href="{{ path('export_entries', { 'category': currentRoute, 'format': 'xml', 'tag' : currentTag }) }}">XML</a></li>{% endif %}
</ul>

View file

@ -275,10 +275,6 @@
{{ render(controller( "WallabagCoreBundle:Tag:addTagForm", { 'id': entry.id } )) }}
</div>
{% if entry.previewPicture is not null %}
<div><img class="preview" src="{{ entry.previewPicture }}" alt="{{ entry.title|striptags|default('entry.default_title'|trans)|raw }}" /></div>
{% endif %}
</aside>
<article>
{{ entry.content | raw }}

View file

@ -20,15 +20,14 @@ class Utils
}
/**
* For a given text, we calculate reading time for an article
* based on 200 words per minute.
* For a given text, we calculate reading time for an article based on 200 words per minute.
*
* @param $text
* @param string $text
*
* @return float
*/
public static function getReadingTime($text)
{
return floor(\count(preg_split('~[^\p{L}\p{N}\']+~u', strip_tags($text))) / 200);
return floor(\count(preg_split('~([^\p{L}\p{N}\']+|(\p{Han}|\p{Hiragana}|\p{Katakana}|\p{Hangul}){1,2})~u', strip_tags($text))) / 200);
}
}

View file

@ -52,6 +52,13 @@ abstract class AbstractConsumer
$this->import->setUser($user);
if (false === $this->import->validateEntry($storedEntry)) {
$this->logger->warning('Entry is invalid', ['entry' => $storedEntry]);
// return true to skip message
return true;
}
$entry = $this->import->parseEntry($storedEntry);
if (null === $entry) {

View file

@ -118,6 +118,15 @@ abstract class AbstractImport implements ImportInterface
*/
abstract public function parseEntry(array $importedEntry);
/**
* Validate that an entry is valid (like has some required keys, etc.).
*
* @param array $importedEntry
*
* @return bool
*/
abstract public function validateEntry(array $importedEntry);
/**
* Fetch content from the ContentProxy (using graby).
* If it fails return the given entry to be saved in all case (to avoid user to loose the content).
@ -141,9 +150,9 @@ abstract class AbstractImport implements ImportInterface
/**
* Parse and insert all given entries.
*
* @param $entries
* @param array $entries
*/
protected function parseEntries($entries)
protected function parseEntries(array $entries)
{
$i = 1;
$entryToBeFlushed = [];
@ -153,6 +162,10 @@ abstract class AbstractImport implements ImportInterface
$importedEntry = $this->setEntryAsRead($importedEntry);
}
if (false === $this->validateEntry($importedEntry)) {
continue;
}
$entry = $this->parseEntry($importedEntry);
if (null === $entry) {

View file

@ -149,9 +149,9 @@ abstract class BrowserImport extends AbstractImport
/**
* Parse and insert all given entries.
*
* @param $entries
* @param array $entries
*/
protected function parseEntries($entries)
protected function parseEntries(array $entries)
{
$i = 1;
$entryToBeFlushed = [];

View file

@ -30,6 +30,18 @@ class ChromeImport extends BrowserImport
return 'import.chrome.description';
}
/**
* {@inheritdoc}
*/
public function validateEntry(array $importedEntry)
{
if (empty($importedEntry['url'])) {
return false;
}
return true;
}
/**
* {@inheritdoc}
*/

View file

@ -30,6 +30,18 @@ class FirefoxImport extends BrowserImport
return 'import.firefox.description';
}
/**
* {@inheritdoc}
*/
public function validateEntry(array $importedEntry)
{
if (empty($importedEntry['uri'])) {
return false;
}
return true;
}
/**
* {@inheritdoc}
*/

View file

@ -105,6 +105,18 @@ class InstapaperImport extends AbstractImport
return true;
}
/**
* {@inheritdoc}
*/
public function validateEntry(array $importedEntry)
{
if (empty($importedEntry['url'])) {
return false;
}
return true;
}
/**
* {@inheritdoc}
*/

View file

@ -80,6 +80,18 @@ class PinboardImport extends AbstractImport
return true;
}
/**
* {@inheritdoc}
*/
public function validateEntry(array $importedEntry)
{
if (empty($importedEntry['href'])) {
return false;
}
return true;
}
/**
* {@inheritdoc}
*/

View file

@ -168,6 +168,18 @@ class PocketImport extends AbstractImport
$this->client = $client;
}
/**
* {@inheritdoc}
*/
public function validateEntry(array $importedEntry)
{
if (empty($importedEntry['resolved_url']) && empty($importedEntry['given_url'])) {
return false;
}
return true;
}
/**
* {@inheritdoc}
*

View file

@ -80,6 +80,18 @@ class ReadabilityImport extends AbstractImport
return true;
}
/**
* {@inheritdoc}
*/
public function validateEntry(array $importedEntry)
{
if (empty($importedEntry['article__url'])) {
return false;
}
return true;
}
/**
* {@inheritdoc}
*/

View file

@ -86,6 +86,18 @@ abstract class WallabagImport extends AbstractImport
return $this;
}
/**
* {@inheritdoc}
*/
public function validateEntry(array $importedEntry)
{
if (empty($importedEntry['url'])) {
return false;
}
return true;
}
/**
* {@inheritdoc}
*/

View file

@ -11,7 +11,7 @@
<main class="valign-wrapper">
<div class="valign row">
<div class="card sw">
<div class="center"><img src="{{ asset('wallassets/themes/_global/img/logo-wallabag.svg') }}" alt="wallabag logo" class="typo-logo" /></div>
<div class="center"><img src="{{ asset('wallassets/themes/_global/img/logo-wallabag.svg') }}" class="typo-logo" alt="wallabag logo" /></div>
{% block fos_user_content %}
{% endblock fos_user_content %}
</div>