aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--src/Wallabag/ApiBundle/Controller/TagRestController.php22
-rw-r--r--src/Wallabag/CoreBundle/DataFixtures/ORM/LoadEntryData.php197
-rw-r--r--src/Wallabag/CoreBundle/DataFixtures/ORM/LoadTagData.php43
-rw-r--r--src/Wallabag/CoreBundle/Helper/EntriesExport.php10
-rw-r--r--src/Wallabag/CoreBundle/Repository/TagRepository.php53
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/themes/baggy/Entry/entries.html.twig4
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/entries.html.twig4
-rw-r--r--src/Wallabag/CoreBundle/Tools/Utils.php7
-rw-r--r--src/Wallabag/ImportBundle/Consumer/AbstractConsumer.php7
-rw-r--r--src/Wallabag/ImportBundle/Import/AbstractImport.php17
-rw-r--r--src/Wallabag/ImportBundle/Import/BrowserImport.php4
-rw-r--r--src/Wallabag/ImportBundle/Import/ChromeImport.php12
-rw-r--r--src/Wallabag/ImportBundle/Import/FirefoxImport.php12
-rw-r--r--src/Wallabag/ImportBundle/Import/InstapaperImport.php12
-rw-r--r--src/Wallabag/ImportBundle/Import/PinboardImport.php12
-rw-r--r--src/Wallabag/ImportBundle/Import/PocketImport.php12
-rw-r--r--src/Wallabag/ImportBundle/Import/ReadabilityImport.php12
-rw-r--r--src/Wallabag/ImportBundle/Import/WallabagImport.php12
-rw-r--r--tests/Wallabag/ApiBundle/Controller/TagRestControllerTest.php46
-rw-r--r--tests/Wallabag/CoreBundle/Tools/UtilsTest.php16
-rw-r--r--tests/Wallabag/CoreBundle/Tools/samples/README5
-rw-r--r--tests/Wallabag/CoreBundle/Tools/samples/chinese.txt10
-rw-r--r--tests/Wallabag/CoreBundle/Tools/samples/cyrillic.txt5
-rw-r--r--tests/Wallabag/CoreBundle/Tools/samples/greek.txt5
-rw-r--r--tests/Wallabag/CoreBundle/Tools/samples/japanese.txt10
-rw-r--r--tests/Wallabag/CoreBundle/Tools/samples/korean.txt10
-rw-r--r--tests/Wallabag/CoreBundle/Tools/samples/latin.txt5
27 files changed, 397 insertions, 167 deletions
diff --git a/src/Wallabag/ApiBundle/Controller/TagRestController.php b/src/Wallabag/ApiBundle/Controller/TagRestController.php
index c6d6df6a..f3498f55 100644
--- a/src/Wallabag/ApiBundle/Controller/TagRestController.php
+++ b/src/Wallabag/ApiBundle/Controller/TagRestController.php
@@ -46,12 +46,14 @@ class TagRestController extends WallabagRestController
46 $this->validateAuthentication(); 46 $this->validateAuthentication();
47 $label = $request->get('tag', ''); 47 $label = $request->get('tag', '');
48 48
49 $tag = $this->getDoctrine()->getRepository('WallabagCoreBundle:Tag')->findOneByLabel($label); 49 $tags = $this->getDoctrine()->getRepository('WallabagCoreBundle:Tag')->findByLabelsAndUser([$label], $this->getUser()->getId());
50 50
51 if (empty($tag)) { 51 if (empty($tags)) {
52 throw $this->createNotFoundException('Tag not found'); 52 throw $this->createNotFoundException('Tag not found');
53 } 53 }
54 54
55 $tag = $tags[0];
56
55 $this->getDoctrine() 57 $this->getDoctrine()
56 ->getRepository('WallabagCoreBundle:Entry') 58 ->getRepository('WallabagCoreBundle:Entry')
57 ->removeTag($this->getUser()->getId(), $tag); 59 ->removeTag($this->getUser()->getId(), $tag);
@@ -80,15 +82,7 @@ class TagRestController extends WallabagRestController
80 82
81 $tagsLabels = $request->get('tags', ''); 83 $tagsLabels = $request->get('tags', '');
82 84
83 $tags = []; 85 $tags = $this->getDoctrine()->getRepository('WallabagCoreBundle:Tag')->findByLabelsAndUser(explode(',', $tagsLabels), $this->getUser()->getId());
84
85 foreach (explode(',', $tagsLabels) as $tagLabel) {
86 $tagEntity = $this->getDoctrine()->getRepository('WallabagCoreBundle:Tag')->findOneByLabel($tagLabel);
87
88 if (!empty($tagEntity)) {
89 $tags[] = $tagEntity;
90 }
91 }
92 86
93 if (empty($tags)) { 87 if (empty($tags)) {
94 throw $this->createNotFoundException('Tags not found'); 88 throw $this->createNotFoundException('Tags not found');
@@ -120,6 +114,12 @@ class TagRestController extends WallabagRestController
120 { 114 {
121 $this->validateAuthentication(); 115 $this->validateAuthentication();
122 116
117 $tagFromDb = $this->getDoctrine()->getRepository('WallabagCoreBundle:Tag')->findByLabelsAndUser([$tag->getLabel()], $this->getUser()->getId());
118
119 if (empty($tagFromDb)) {
120 throw $this->createNotFoundException('Tag not found');
121 }
122
123 $this->getDoctrine() 123 $this->getDoctrine()
124 ->getRepository('WallabagCoreBundle:Entry') 124 ->getRepository('WallabagCoreBundle:Entry')
125 ->removeTag($this->getUser()->getId(), $tag); 125 ->removeTag($this->getUser()->getId(), $tag);
diff --git a/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadEntryData.php b/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadEntryData.php
index 0e1510a2..8e7a1d2a 100644
--- a/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadEntryData.php
+++ b/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadEntryData.php
@@ -14,97 +14,112 @@ class LoadEntryData extends AbstractFixture implements OrderedFixtureInterface
14 */ 14 */
15 public function load(ObjectManager $manager) 15 public function load(ObjectManager $manager)
16 { 16 {
17 $entry1 = new Entry($this->getReference('admin-user')); 17 $entries = [
18 $entry1->setUrl('http://0.0.0.0/entry1'); 18 'entry1' => [
19 $entry1->setReadingTime(11); 19 'user' => 'admin-user',
20 $entry1->setDomainName('domain.io'); 20 'url' => 'http://0.0.0.0/entry1',
21 $entry1->setMimetype('text/html'); 21 'reading_time' => 11,
22 $entry1->setTitle('test title entry1'); 22 'domain' => 'domain.io',
23 $entry1->setContent('This is my content /o/'); 23 'mime' => 'text/html',
24 $entry1->setLanguage('en'); 24 'title' => 'test title entry1',
25 25 'content' => 'This is my content /o/',
26 $entry1->addTag($this->getReference('foo-tag')); 26 'language' => 'en',
27 $entry1->addTag($this->getReference('baz-tag')); 27 'tags' => ['foo-tag', 'baz-tag'],
28 28 ],
29 $manager->persist($entry1); 29 'entry2' => [
30 30 'user' => 'admin-user',
31 $this->addReference('entry1', $entry1); 31 'url' => 'http://0.0.0.0/entry2',
32 32 'reading_time' => 1,
33 $entry2 = new Entry($this->getReference('admin-user')); 33 'domain' => 'domain.io',
34 $entry2->setUrl('http://0.0.0.0/entry2'); 34 'mime' => 'text/html',
35 $entry2->setReadingTime(1); 35 'title' => 'test title entry2',
36 $entry2->setDomainName('domain.io'); 36 'content' => 'This is my content /o/',
37 $entry2->setMimetype('text/html'); 37 'origin' => 'ftp://oneftp.tld',
38 $entry2->setTitle('test title entry2'); 38 'language' => 'fr',
39 $entry2->setContent('This is my content /o/'); 39 ],
40 $entry2->setOriginUrl('ftp://oneftp.tld'); 40 'entry3' => [
41 $entry2->setLanguage('fr'); 41 'user' => 'bob-user',
42 42 'url' => 'http://0.0.0.0/entry3',
43 $manager->persist($entry2); 43 'reading_time' => 1,
44 44 'domain' => 'domain.io',
45 $this->addReference('entry2', $entry2); 45 'mime' => 'text/html',
46 46 'title' => 'test title entry3',
47 $entry3 = new Entry($this->getReference('bob-user')); 47 'content' => 'This is my content /o/',
48 $entry3->setUrl('http://0.0.0.0/entry3'); 48 'language' => 'en',
49 $entry3->setReadingTime(1); 49 'tags' => ['foo-tag', 'bar-tag', 'bob-tag'],
50 $entry3->setDomainName('domain.io'); 50 ],
51 $entry3->setMimetype('text/html'); 51 'entry4' => [
52 $entry3->setTitle('test title entry3'); 52 'user' => 'admin-user',
53 $entry3->setContent('This is my content /o/'); 53 'url' => 'http://0.0.0.0/entry4',
54 $entry3->setLanguage('en'); 54 'reading_time' => 12,
55 55 'domain' => 'domain.io',
56 $entry3->addTag($this->getReference('foo-tag')); 56 'mime' => 'text/html',
57 $entry3->addTag($this->getReference('bar-tag')); 57 'title' => 'test title entry4',
58 58 'content' => 'This is my content /o/',
59 $manager->persist($entry3); 59 'language' => 'en',
60 60 'tags' => ['foo-tag', 'bar-tag'],
61 $this->addReference('entry3', $entry3); 61 ],
62 62 'entry5' => [
63 $entry4 = new Entry($this->getReference('admin-user')); 63 'user' => 'admin-user',
64 $entry4->setUrl('http://0.0.0.0/entry4'); 64 'url' => 'http://0.0.0.0/entry5',
65 $entry4->setReadingTime(12); 65 'reading_time' => 12,
66 $entry4->setDomainName('domain.io'); 66 'domain' => 'domain.io',
67 $entry4->setMimetype('text/html'); 67 'mime' => 'text/html',
68 $entry4->setTitle('test title entry4'); 68 'title' => 'test title entry5',
69 $entry4->setContent('This is my content /o/'); 69 'content' => 'This is my content /o/',
70 $entry4->setLanguage('en'); 70 'language' => 'fr',
71 71 'starred' => true,
72 $entry4->addTag($this->getReference('foo-tag')); 72 'preview' => 'http://0.0.0.0/image.jpg',
73 $entry4->addTag($this->getReference('bar-tag')); 73 ],
74 74 'entry6' => [
75 $manager->persist($entry4); 75 'user' => 'admin-user',
76 76 'url' => 'http://0.0.0.0/entry6',
77 $this->addReference('entry4', $entry4); 77 'reading_time' => 12,
78 78 'domain' => 'domain.io',
79 $entry5 = new Entry($this->getReference('admin-user')); 79 'mime' => 'text/html',
80 $entry5->setUrl('http://0.0.0.0/entry5'); 80 'title' => 'test title entry6',
81 $entry5->setReadingTime(12); 81 'content' => 'This is my content /o/',
82 $entry5->setDomainName('domain.io'); 82 'language' => 'de',
83 $entry5->setMimetype('text/html'); 83 'archived' => true,
84 $entry5->setTitle('test title entry5'); 84 'tags' => ['bar-tag'],
85 $entry5->setContent('This is my content /o/'); 85 ],
86 $entry5->setStarred(true); 86 ];
87 $entry5->setLanguage('fr'); 87
88 $entry5->setPreviewPicture('http://0.0.0.0/image.jpg'); 88 foreach ($entries as $reference => $item) {
89 89 $entry = new Entry($this->getReference($item['user']));
90 $manager->persist($entry5); 90 $entry->setUrl($item['url']);
91 91 $entry->setReadingTime($item['reading_time']);
92 $this->addReference('entry5', $entry5); 92 $entry->setDomainName($item['domain']);
93 93 $entry->setMimetype($item['mime']);
94 $entry6 = new Entry($this->getReference('admin-user')); 94 $entry->setTitle($item['title']);
95 $entry6->setUrl('http://0.0.0.0/entry6'); 95 $entry->setContent($item['content']);
96 $entry6->setReadingTime(12); 96 $entry->setLanguage($item['language']);
97 $entry6->setDomainName('domain.io'); 97
98 $entry6->setMimetype('text/html'); 98 if (isset($item['tags'])) {
99 $entry6->setTitle('test title entry6'); 99 foreach ($item['tags'] as $tag) {
100 $entry6->setContent('This is my content /o/'); 100 $entry->addTag($this->getReference($tag));
101 $entry6->setArchived(true); 101 }
102 $entry6->setLanguage('de'); 102 }
103 $entry6->addTag($this->getReference('bar-tag')); 103
104 104 if (isset($item['origin'])) {
105 $manager->persist($entry6); 105 $entry->setOriginUrl($item['origin']);
106 106 }
107 $this->addReference('entry6', $entry6); 107
108 if (isset($item['starred'])) {
109 $entry->setStarred($item['starred']);
110 }
111
112 if (isset($item['archived'])) {
113 $entry->setArchived($item['archived']);
114 }
115
116 if (isset($item['preview'])) {
117 $entry->setPreviewPicture($item['preview']);
118 }
119
120 $manager->persist($entry);
121 $this->addReference($reference, $entry);
122 }
108 123
109 $manager->flush(); 124 $manager->flush();
110 } 125 }
diff --git a/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadTagData.php b/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadTagData.php
index 0ecfd18b..485445c1 100644
--- a/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadTagData.php
+++ b/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadTagData.php
@@ -14,33 +14,22 @@ class LoadTagData extends AbstractFixture implements OrderedFixtureInterface
14 */ 14 */
15 public function load(ObjectManager $manager) 15 public function load(ObjectManager $manager)
16 { 16 {
17 $tag1 = new Tag(); 17 $tags = [
18 $tag1->setLabel('foo bar'); 18 'foo-bar-tag' => 'foo bar', //tag used for EntryControllerTest
19 19 'bar-tag' => 'bar',
20 $manager->persist($tag1); 20 'baz-tag' => 'baz', // tag used for ExportControllerTest
21 21 'foo-tag' => 'foo',
22 $this->addReference('foo-bar-tag', $tag1); 22 'bob-tag' => 'bob', // tag used for TagRestControllerTest
23 23 ];
24 $tag2 = new Tag(); 24
25 $tag2->setLabel('bar'); 25 foreach ($tags as $reference => $label) {
26 26 $tag = new Tag();
27 $manager->persist($tag2); 27 $tag->setLabel($label);
28 28
29 $this->addReference('bar-tag', $tag2); 29 $manager->persist($tag);
30 30
31 $tag3 = new Tag(); 31 $this->addReference($reference, $tag);
32 $tag3->setLabel('baz'); 32 }
33
34 $manager->persist($tag3);
35
36 $this->addReference('baz-tag', $tag3);
37
38 $tag4 = new Tag();
39 $tag4->setLabel('foo');
40
41 $manager->persist($tag4);
42
43 $this->addReference('foo-tag', $tag4);
44 33
45 $manager->flush(); 34 $manager->flush();
46 } 35 }
diff --git a/src/Wallabag/CoreBundle/Helper/EntriesExport.php b/src/Wallabag/CoreBundle/Helper/EntriesExport.php
index cbf1037b..6082f6b9 100644
--- a/src/Wallabag/CoreBundle/Helper/EntriesExport.php
+++ b/src/Wallabag/CoreBundle/Helper/EntriesExport.php
@@ -150,8 +150,6 @@ class EntriesExport
150 */ 150 */
151 151
152 $book->setTitle($this->title); 152 $book->setTitle($this->title);
153 // Could also be the ISBN number, prefered for published books, or a UUID.
154 $book->setIdentifier($this->title, EPub::IDENTIFIER_URI);
155 // 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. 153 // 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.
156 $book->setLanguage($this->language); 154 $book->setLanguage($this->language);
157 $book->setDescription('Some articles saved on my wallabag'); 155 $book->setDescription('Some articles saved on my wallabag');
@@ -174,6 +172,8 @@ class EntriesExport
174 $book->setCoverImage('Cover.png', file_get_contents($this->logoPath), 'image/png'); 172 $book->setCoverImage('Cover.png', file_get_contents($this->logoPath), 'image/png');
175 } 173 }
176 174
175 $entryIds = [];
176
177 /* 177 /*
178 * Adding actual entries 178 * Adding actual entries
179 */ 179 */
@@ -192,8 +192,14 @@ class EntriesExport
192 $book->addChapter('Title', 'Title.html', $titlepage, true, EPub::EXTERNAL_REF_ADD); 192 $book->addChapter('Title', 'Title.html', $titlepage, true, EPub::EXTERNAL_REF_ADD);
193 $chapter = $content_start . $entry->getContent() . $bookEnd; 193 $chapter = $content_start . $entry->getContent() . $bookEnd;
194 $book->addChapter($entry->getTitle(), htmlspecialchars($filename) . '.html', $chapter, true, EPub::EXTERNAL_REF_ADD); 194 $book->addChapter($entry->getTitle(), htmlspecialchars($filename) . '.html', $chapter, true, EPub::EXTERNAL_REF_ADD);
195
196 $entryIds[] = $entry->getId();
195 } 197 }
196 198
199 // Could also be the ISBN number, prefered for published books, or a UUID.
200 $hash = sha1(sprintf('%s:%s', $this->wallabagUrl, implode(',', $entryIds)));
201 $book->setIdentifier(sprintf('urn:wallabag:%s', $hash), EPub::IDENTIFIER_URI);
202
197 $book->buildTOC(); 203 $book->buildTOC();
198 204
199 return Response::create( 205 return Response::create(
diff --git a/src/Wallabag/CoreBundle/Repository/TagRepository.php b/src/Wallabag/CoreBundle/Repository/TagRepository.php
index 3ae9d414..8464a6a5 100644
--- a/src/Wallabag/CoreBundle/Repository/TagRepository.php
+++ b/src/Wallabag/CoreBundle/Repository/TagRepository.php
@@ -3,6 +3,7 @@
3namespace Wallabag\CoreBundle\Repository; 3namespace Wallabag\CoreBundle\Repository;
4 4
5use Doctrine\ORM\EntityRepository; 5use Doctrine\ORM\EntityRepository;
6use Doctrine\ORM\QueryBuilder;
6use Wallabag\CoreBundle\Entity\Tag; 7use Wallabag\CoreBundle\Entity\Tag;
7 8
8class TagRepository extends EntityRepository 9class TagRepository extends EntityRepository
@@ -45,12 +46,8 @@ class TagRepository extends EntityRepository
45 */ 46 */
46 public function findAllTags($userId) 47 public function findAllTags($userId)
47 { 48 {
48 $ids = $this->createQueryBuilder('t') 49 $ids = $this->getQueryBuilderByUser($userId)
49 ->select('t.id') 50 ->select('t.id')
50 ->leftJoin('t.entries', 'e')
51 ->where('e.user = :userId')->setParameter('userId', $userId)
52 ->groupBy('t.id')
53 ->orderBy('t.slug')
54 ->getQuery() 51 ->getQuery()
55 ->getArrayResult(); 52 ->getArrayResult();
56 53
@@ -71,18 +68,30 @@ class TagRepository extends EntityRepository
71 */ 68 */
72 public function findAllFlatTagsWithNbEntries($userId) 69 public function findAllFlatTagsWithNbEntries($userId)
73 { 70 {
74 return $this->createQueryBuilder('t') 71 return $this->getQueryBuilderByUser($userId)
75 ->select('t.id, t.label, t.slug, count(e.id) as nbEntries') 72 ->select('t.id, t.label, t.slug, count(e.id) as nbEntries')
76 ->distinct(true) 73 ->distinct(true)
77 ->leftJoin('t.entries', 'e')
78 ->where('e.user = :userId')
79 ->groupBy('t.id')
80 ->orderBy('t.slug')
81 ->setParameter('userId', $userId)
82 ->getQuery() 74 ->getQuery()
83 ->getArrayResult(); 75 ->getArrayResult();
84 } 76 }
85 77
78 public function findByLabelsAndUser($labels, $userId)
79 {
80 $qb = $this->getQueryBuilderByUser($userId)
81 ->select('t.id');
82
83 $ids = $qb->andWhere($qb->expr()->in('t.label', $labels))
84 ->getQuery()
85 ->getArrayResult();
86
87 $tags = [];
88 foreach ($ids as $id) {
89 $tags[] = $this->find($id);
90 }
91
92 return $tags;
93 }
94
86 /** 95 /**
87 * Used only in test case to get a tag for our entry. 96 * Used only in test case to get a tag for our entry.
88 * 97 *
@@ -101,13 +110,9 @@ class TagRepository extends EntityRepository
101 110
102 public function findForArchivedArticlesByUser($userId) 111 public function findForArchivedArticlesByUser($userId)
103 { 112 {
104 $ids = $this->createQueryBuilder('t') 113 $ids = $this->getQueryBuilderByUser($userId)
105 ->select('t.id') 114 ->select('t.id')
106 ->leftJoin('t.entries', 'e')
107 ->where('e.user = :userId')->setParameter('userId', $userId)
108 ->andWhere('e.isArchived = true') 115 ->andWhere('e.isArchived = true')
109 ->groupBy('t.id')
110 ->orderBy('t.slug')
111 ->getQuery() 116 ->getQuery()
112 ->getArrayResult(); 117 ->getArrayResult();
113 118
@@ -118,4 +123,20 @@ class TagRepository extends EntityRepository
118 123
119 return $tags; 124 return $tags;
120 } 125 }
126
127 /**
128 * Retrieve a sorted list of tags used by a user.
129 *
130 * @param int $userId
131 *
132 * @return QueryBuilder
133 */
134 private function getQueryBuilderByUser($userId)
135 {
136 return $this->createQueryBuilder('t')
137 ->leftJoin('t.entries', 'e')
138 ->where('e.user = :userId')->setParameter('userId', $userId)
139 ->groupBy('t.id')
140 ->orderBy('t.slug');
141 }
121} 142}
diff --git a/src/Wallabag/CoreBundle/Resources/views/themes/baggy/Entry/entries.html.twig b/src/Wallabag/CoreBundle/Resources/views/themes/baggy/Entry/entries.html.twig
index cfc6644b..832112be 100644
--- a/src/Wallabag/CoreBundle/Resources/views/themes/baggy/Entry/entries.html.twig
+++ b/src/Wallabag/CoreBundle/Resources/views/themes/baggy/Entry/entries.html.twig
@@ -99,8 +99,8 @@
99 {% 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 %} 99 {% 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 %}
100 {% 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 %} 100 {% 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 %}
101 {% 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 %} 101 {% 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 %}
102 {% 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 %} 102 {% 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 %}
103 {% 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 %} 103 {% 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 %}
104 {% 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 %} 104 {% 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 %}
105 {% 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 %} 105 {% 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 %}
106 </ul> 106 </ul>
diff --git a/src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/entries.html.twig b/src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/entries.html.twig
index a137f3c3..742dd330 100644
--- a/src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/entries.html.twig
+++ b/src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/entries.html.twig
@@ -68,8 +68,8 @@
68 {% 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 %} 68 {% 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 %}
69 {% 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 %} 69 {% 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 %}
70 {% 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 %} 70 {% 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 %}
71 {% 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 %} 71 {% 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 %}
72 {% 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 %} 72 {% 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 %}
73 {% 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 %} 73 {% 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 %}
74 {% 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 %} 74 {% 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 %}
75 </ul> 75 </ul>
diff --git a/src/Wallabag/CoreBundle/Tools/Utils.php b/src/Wallabag/CoreBundle/Tools/Utils.php
index 46bb1dc5..e56e251e 100644
--- a/src/Wallabag/CoreBundle/Tools/Utils.php
+++ b/src/Wallabag/CoreBundle/Tools/Utils.php
@@ -20,15 +20,14 @@ class Utils
20 } 20 }
21 21
22 /** 22 /**
23 * For a given text, we calculate reading time for an article 23 * For a given text, we calculate reading time for an article based on 200 words per minute.
24 * based on 200 words per minute.
25 * 24 *
26 * @param $text 25 * @param string $text
27 * 26 *
28 * @return float 27 * @return float
29 */ 28 */
30 public static function getReadingTime($text) 29 public static function getReadingTime($text)
31 { 30 {
32 return floor(\count(preg_split('~[^\p{L}\p{N}\']+~u', strip_tags($text))) / 200); 31 return floor(\count(preg_split('~([^\p{L}\p{N}\']+|(\p{Han}|\p{Hiragana}|\p{Katakana}|\p{Hangul}){1,2})~u', strip_tags($text))) / 200);
33 } 32 }
34} 33}
diff --git a/src/Wallabag/ImportBundle/Consumer/AbstractConsumer.php b/src/Wallabag/ImportBundle/Consumer/AbstractConsumer.php
index b035f5cc..e4bfbdf0 100644
--- a/src/Wallabag/ImportBundle/Consumer/AbstractConsumer.php
+++ b/src/Wallabag/ImportBundle/Consumer/AbstractConsumer.php
@@ -52,6 +52,13 @@ abstract class AbstractConsumer
52 52
53 $this->import->setUser($user); 53 $this->import->setUser($user);
54 54
55 if (false === $this->import->validateEntry($storedEntry)) {
56 $this->logger->warning('Entry is invalid', ['entry' => $storedEntry]);
57
58 // return true to skip message
59 return true;
60 }
61
55 $entry = $this->import->parseEntry($storedEntry); 62 $entry = $this->import->parseEntry($storedEntry);
56 63
57 if (null === $entry) { 64 if (null === $entry) {
diff --git a/src/Wallabag/ImportBundle/Import/AbstractImport.php b/src/Wallabag/ImportBundle/Import/AbstractImport.php
index 58a234f4..d39d71b6 100644
--- a/src/Wallabag/ImportBundle/Import/AbstractImport.php
+++ b/src/Wallabag/ImportBundle/Import/AbstractImport.php
@@ -119,6 +119,15 @@ abstract class AbstractImport implements ImportInterface
119 abstract public function parseEntry(array $importedEntry); 119 abstract public function parseEntry(array $importedEntry);
120 120
121 /** 121 /**
122 * Validate that an entry is valid (like has some required keys, etc.).
123 *
124 * @param array $importedEntry
125 *
126 * @return bool
127 */
128 abstract public function validateEntry(array $importedEntry);
129
130 /**
122 * Fetch content from the ContentProxy (using graby). 131 * Fetch content from the ContentProxy (using graby).
123 * If it fails return the given entry to be saved in all case (to avoid user to loose the content). 132 * If it fails return the given entry to be saved in all case (to avoid user to loose the content).
124 * 133 *
@@ -141,9 +150,9 @@ abstract class AbstractImport implements ImportInterface
141 /** 150 /**
142 * Parse and insert all given entries. 151 * Parse and insert all given entries.
143 * 152 *
144 * @param $entries 153 * @param array $entries
145 */ 154 */
146 protected function parseEntries($entries) 155 protected function parseEntries(array $entries)
147 { 156 {
148 $i = 1; 157 $i = 1;
149 $entryToBeFlushed = []; 158 $entryToBeFlushed = [];
@@ -153,6 +162,10 @@ abstract class AbstractImport implements ImportInterface
153 $importedEntry = $this->setEntryAsRead($importedEntry); 162 $importedEntry = $this->setEntryAsRead($importedEntry);
154 } 163 }
155 164
165 if (false === $this->validateEntry($importedEntry)) {
166 continue;
167 }
168
156 $entry = $this->parseEntry($importedEntry); 169 $entry = $this->parseEntry($importedEntry);
157 170
158 if (null === $entry) { 171 if (null === $entry) {
diff --git a/src/Wallabag/ImportBundle/Import/BrowserImport.php b/src/Wallabag/ImportBundle/Import/BrowserImport.php
index 225f1791..4678ae0c 100644
--- a/src/Wallabag/ImportBundle/Import/BrowserImport.php
+++ b/src/Wallabag/ImportBundle/Import/BrowserImport.php
@@ -149,9 +149,9 @@ abstract class BrowserImport extends AbstractImport
149 /** 149 /**
150 * Parse and insert all given entries. 150 * Parse and insert all given entries.
151 * 151 *
152 * @param $entries 152 * @param array $entries
153 */ 153 */
154 protected function parseEntries($entries) 154 protected function parseEntries(array $entries)
155 { 155 {
156 $i = 1; 156 $i = 1;
157 $entryToBeFlushed = []; 157 $entryToBeFlushed = [];
diff --git a/src/Wallabag/ImportBundle/Import/ChromeImport.php b/src/Wallabag/ImportBundle/Import/ChromeImport.php
index 09183abe..eccee698 100644
--- a/src/Wallabag/ImportBundle/Import/ChromeImport.php
+++ b/src/Wallabag/ImportBundle/Import/ChromeImport.php
@@ -33,6 +33,18 @@ class ChromeImport extends BrowserImport
33 /** 33 /**
34 * {@inheritdoc} 34 * {@inheritdoc}
35 */ 35 */
36 public function validateEntry(array $importedEntry)
37 {
38 if (empty($importedEntry['url'])) {
39 return false;
40 }
41
42 return true;
43 }
44
45 /**
46 * {@inheritdoc}
47 */
36 protected function prepareEntry(array $entry = []) 48 protected function prepareEntry(array $entry = [])
37 { 49 {
38 $data = [ 50 $data = [
diff --git a/src/Wallabag/ImportBundle/Import/FirefoxImport.php b/src/Wallabag/ImportBundle/Import/FirefoxImport.php
index 73269fe1..8999e3f3 100644
--- a/src/Wallabag/ImportBundle/Import/FirefoxImport.php
+++ b/src/Wallabag/ImportBundle/Import/FirefoxImport.php
@@ -33,6 +33,18 @@ class FirefoxImport extends BrowserImport
33 /** 33 /**
34 * {@inheritdoc} 34 * {@inheritdoc}
35 */ 35 */
36 public function validateEntry(array $importedEntry)
37 {
38 if (empty($importedEntry['uri'])) {
39 return false;
40 }
41
42 return true;
43 }
44
45 /**
46 * {@inheritdoc}
47 */
36 protected function prepareEntry(array $entry = []) 48 protected function prepareEntry(array $entry = [])
37 { 49 {
38 $data = [ 50 $data = [
diff --git a/src/Wallabag/ImportBundle/Import/InstapaperImport.php b/src/Wallabag/ImportBundle/Import/InstapaperImport.php
index e4f0970c..5a18c7c0 100644
--- a/src/Wallabag/ImportBundle/Import/InstapaperImport.php
+++ b/src/Wallabag/ImportBundle/Import/InstapaperImport.php
@@ -108,6 +108,18 @@ class InstapaperImport extends AbstractImport
108 /** 108 /**
109 * {@inheritdoc} 109 * {@inheritdoc}
110 */ 110 */
111 public function validateEntry(array $importedEntry)
112 {
113 if (empty($importedEntry['url'])) {
114 return false;
115 }
116
117 return true;
118 }
119
120 /**
121 * {@inheritdoc}
122 */
111 public function parseEntry(array $importedEntry) 123 public function parseEntry(array $importedEntry)
112 { 124 {
113 $existingEntry = $this->em 125 $existingEntry = $this->em
diff --git a/src/Wallabag/ImportBundle/Import/PinboardImport.php b/src/Wallabag/ImportBundle/Import/PinboardImport.php
index 110b0464..995d1f2c 100644
--- a/src/Wallabag/ImportBundle/Import/PinboardImport.php
+++ b/src/Wallabag/ImportBundle/Import/PinboardImport.php
@@ -83,6 +83,18 @@ class PinboardImport extends AbstractImport
83 /** 83 /**
84 * {@inheritdoc} 84 * {@inheritdoc}
85 */ 85 */
86 public function validateEntry(array $importedEntry)
87 {
88 if (empty($importedEntry['href'])) {
89 return false;
90 }
91
92 return true;
93 }
94
95 /**
96 * {@inheritdoc}
97 */
86 public function parseEntry(array $importedEntry) 98 public function parseEntry(array $importedEntry)
87 { 99 {
88 $existingEntry = $this->em 100 $existingEntry = $this->em
diff --git a/src/Wallabag/ImportBundle/Import/PocketImport.php b/src/Wallabag/ImportBundle/Import/PocketImport.php
index c1b35b7e..d3643389 100644
--- a/src/Wallabag/ImportBundle/Import/PocketImport.php
+++ b/src/Wallabag/ImportBundle/Import/PocketImport.php
@@ -170,6 +170,18 @@ class PocketImport extends AbstractImport
170 170
171 /** 171 /**
172 * {@inheritdoc} 172 * {@inheritdoc}
173 */
174 public function validateEntry(array $importedEntry)
175 {
176 if (empty($importedEntry['resolved_url']) && empty($importedEntry['given_url'])) {
177 return false;
178 }
179
180 return true;
181 }
182
183 /**
184 * {@inheritdoc}
173 * 185 *
174 * @see https://getpocket.com/developer/docs/v3/retrieve 186 * @see https://getpocket.com/developer/docs/v3/retrieve
175 */ 187 */
diff --git a/src/Wallabag/ImportBundle/Import/ReadabilityImport.php b/src/Wallabag/ImportBundle/Import/ReadabilityImport.php
index 002b27f4..a5f3798e 100644
--- a/src/Wallabag/ImportBundle/Import/ReadabilityImport.php
+++ b/src/Wallabag/ImportBundle/Import/ReadabilityImport.php
@@ -83,6 +83,18 @@ class ReadabilityImport extends AbstractImport
83 /** 83 /**
84 * {@inheritdoc} 84 * {@inheritdoc}
85 */ 85 */
86 public function validateEntry(array $importedEntry)
87 {
88 if (empty($importedEntry['article__url'])) {
89 return false;
90 }
91
92 return true;
93 }
94
95 /**
96 * {@inheritdoc}
97 */
86 public function parseEntry(array $importedEntry) 98 public function parseEntry(array $importedEntry)
87 { 99 {
88 $existingEntry = $this->em 100 $existingEntry = $this->em
diff --git a/src/Wallabag/ImportBundle/Import/WallabagImport.php b/src/Wallabag/ImportBundle/Import/WallabagImport.php
index c64ccd64..350d0600 100644
--- a/src/Wallabag/ImportBundle/Import/WallabagImport.php
+++ b/src/Wallabag/ImportBundle/Import/WallabagImport.php
@@ -89,6 +89,18 @@ abstract class WallabagImport extends AbstractImport
89 /** 89 /**
90 * {@inheritdoc} 90 * {@inheritdoc}
91 */ 91 */
92 public function validateEntry(array $importedEntry)
93 {
94 if (empty($importedEntry['url'])) {
95 return false;
96 }
97
98 return true;
99 }
100
101 /**
102 * {@inheritdoc}
103 */
92 public function parseEntry(array $importedEntry) 104 public function parseEntry(array $importedEntry)
93 { 105 {
94 $existingEntry = $this->em 106 $existingEntry = $this->em
diff --git a/tests/Wallabag/ApiBundle/Controller/TagRestControllerTest.php b/tests/Wallabag/ApiBundle/Controller/TagRestControllerTest.php
index 430e548d..9daa94cd 100644
--- a/tests/Wallabag/ApiBundle/Controller/TagRestControllerTest.php
+++ b/tests/Wallabag/ApiBundle/Controller/TagRestControllerTest.php
@@ -7,6 +7,8 @@ use Wallabag\CoreBundle\Entity\Tag;
7 7
8class TagRestControllerTest extends WallabagApiTestCase 8class TagRestControllerTest extends WallabagApiTestCase
9{ 9{
10 private $otherUserTagLabel = 'bob';
11
10 public function testGetUserTags() 12 public function testGetUserTags()
11 { 13 {
12 $this->client->request('GET', '/api/tags.json'); 14 $this->client->request('GET', '/api/tags.json');
@@ -19,17 +21,33 @@ class TagRestControllerTest extends WallabagApiTestCase
19 $this->assertArrayHasKey('id', $content[0]); 21 $this->assertArrayHasKey('id', $content[0]);
20 $this->assertArrayHasKey('label', $content[0]); 22 $this->assertArrayHasKey('label', $content[0]);
21 23
24 $tagLabels = array_map(function ($i) {
25 return $i['label'];
26 }, $content);
27
28 $this->assertNotContains($this->otherUserTagLabel, $tagLabels, 'There is a possible tag leak');
29
22 return end($content); 30 return end($content);
23 } 31 }
24 32
25 public function testDeleteUserTag() 33 public function testDeleteUserTag()
26 { 34 {
35 $em = $this->client->getContainer()->get('doctrine.orm.entity_manager');
36 $entry = $this->client->getContainer()
37 ->get('doctrine.orm.entity_manager')
38 ->getRepository('WallabagCoreBundle:Entry')
39 ->findOneWithTags($this->user->getId());
40
41 $entry = $entry[0];
42
27 $tagLabel = 'tagtest'; 43 $tagLabel = 'tagtest';
28 $tag = new Tag(); 44 $tag = new Tag();
29 $tag->setLabel($tagLabel); 45 $tag->setLabel($tagLabel);
30
31 $em = $this->client->getContainer()->get('doctrine.orm.entity_manager');
32 $em->persist($tag); 46 $em->persist($tag);
47
48 $entry->addTag($tag);
49
50 $em->persist($entry);
33 $em->flush(); 51 $em->flush();
34 $em->clear(); 52 $em->clear();
35 53
@@ -53,6 +71,16 @@ class TagRestControllerTest extends WallabagApiTestCase
53 $this->assertNull($tag, $tagLabel . ' was removed because it begun an orphan tag'); 71 $this->assertNull($tag, $tagLabel . ' was removed because it begun an orphan tag');
54 } 72 }
55 73
74 public function testDeleteOtherUserTag()
75 {
76 $em = $this->client->getContainer()->get('doctrine.orm.entity_manager');
77 $tag = $em->getRepository('WallabagCoreBundle:Tag')->findOneByLabel($this->otherUserTagLabel);
78
79 $this->client->request('DELETE', '/api/tags/' . $tag->getId() . '.json');
80
81 $this->assertSame(404, $this->client->getResponse()->getStatusCode());
82 }
83
56 public function dataForDeletingTagByLabel() 84 public function dataForDeletingTagByLabel()
57 { 85 {
58 return [ 86 return [
@@ -112,6 +140,13 @@ class TagRestControllerTest extends WallabagApiTestCase
112 $this->assertSame(404, $this->client->getResponse()->getStatusCode()); 140 $this->assertSame(404, $this->client->getResponse()->getStatusCode());
113 } 141 }
114 142
143 public function testDeleteTagByLabelOtherUser()
144 {
145 $this->client->request('DELETE', '/api/tag/label.json', ['tag' => $this->otherUserTagLabel]);
146
147 $this->assertSame(404, $this->client->getResponse()->getStatusCode());
148 }
149
115 /** 150 /**
116 * @dataProvider dataForDeletingTagByLabel 151 * @dataProvider dataForDeletingTagByLabel
117 */ 152 */
@@ -180,4 +215,11 @@ class TagRestControllerTest extends WallabagApiTestCase
180 215
181 $this->assertSame(404, $this->client->getResponse()->getStatusCode()); 216 $this->assertSame(404, $this->client->getResponse()->getStatusCode());
182 } 217 }
218
219 public function testDeleteTagsByLabelOtherUser()
220 {
221 $this->client->request('DELETE', '/api/tags/label.json', ['tags' => $this->otherUserTagLabel]);
222
223 $this->assertSame(404, $this->client->getResponse()->getStatusCode());
224 }
183} 225}
diff --git a/tests/Wallabag/CoreBundle/Tools/UtilsTest.php b/tests/Wallabag/CoreBundle/Tools/UtilsTest.php
index 952d076d..c6ed74f0 100644
--- a/tests/Wallabag/CoreBundle/Tools/UtilsTest.php
+++ b/tests/Wallabag/CoreBundle/Tools/UtilsTest.php
@@ -11,9 +11,9 @@ class UtilsTest extends TestCase
11 /** 11 /**
12 * @dataProvider examples 12 * @dataProvider examples
13 */ 13 */
14 public function testCorrectWordsCountForDifferentLanguages($text, $expectedCount) 14 public function testCorrectWordsCountForDifferentLanguages($filename, $text, $expectedCount)
15 { 15 {
16 static::assertSame((float) $expectedCount, Utils::getReadingTime($text)); 16 static::assertSame((float) $expectedCount, Utils::getReadingTime($text), 'Reading time for: ' . $filename);
17 } 17 }
18 18
19 public function examples() 19 public function examples()
@@ -21,7 +21,17 @@ class UtilsTest extends TestCase
21 $examples = []; 21 $examples = [];
22 $finder = (new Finder())->in(__DIR__ . '/samples'); 22 $finder = (new Finder())->in(__DIR__ . '/samples');
23 foreach ($finder->getIterator() as $file) { 23 foreach ($finder->getIterator() as $file) {
24 $examples[] = [$file->getContents(), 1]; 24 preg_match('/-----CONTENT-----\s*(.*?)\s*-----READING_TIME-----\s*(.*)/sx', $file->getContents(), $match);
25
26 if (3 !== \count($match)) {
27 throw new \Exception('Sample file "' . $file->getRelativePathname() . '" as wrong definition, see README.');
28 }
29
30 $examples[] = [
31 $file->getRelativePathname(),
32 $match[1], // content
33 $match[2], // reading time
34 ];
25 } 35 }
26 36
27 return $examples; 37 return $examples;
diff --git a/tests/Wallabag/CoreBundle/Tools/samples/README b/tests/Wallabag/CoreBundle/Tools/samples/README
new file mode 100644
index 00000000..e8f946c0
--- /dev/null
+++ b/tests/Wallabag/CoreBundle/Tools/samples/README
@@ -0,0 +1,5 @@
1Defined language sample should use the following structure:
2
3-----CONTENT-----
4
5-----READING_TIME-----
diff --git a/tests/Wallabag/CoreBundle/Tools/samples/chinese.txt b/tests/Wallabag/CoreBundle/Tools/samples/chinese.txt
new file mode 100644
index 00000000..864603cb
--- /dev/null
+++ b/tests/Wallabag/CoreBundle/Tools/samples/chinese.txt
@@ -0,0 +1,10 @@
1-----CONTENT-----
2职然问讲念谷月挂大报住本読能录要褐込。料士纸木陈与兴组静终図问有。今観深车相环学俳健越増职県県多券报。雪月批导掲稿家缝城间真中崩図人连。前担写治芸面毎作似水州稿注球戦頃。済方宮安目垣強入料会先呼略。計定設負財作覧経己員事田事球岡示差学。最院書模婚金回禁朝船教任分禁検理慮宿。
3
4変送调指式真気交现上様女限宅复。禁业稿者普视想来木残止者済断式安。万致相领鉄再改界逮由竹式元最台変。済问活助库脳部风政京転说区変。文図化仙政常地里芸上褒前読望误记温政信土。惑育候当人万部逮重申結標番業望般。断瀬後社天打日資交献秀世覧第。補当編里身社記利件部夜中心掲大。
5
6时大栗夜测署市要纯京挙化済负品。天最场情算掲放故手茨指岛然渡活民年。第纯交一特问明室试賛际者建。论铜所常縄一広気特秋提公茶可満编旅相変権。
7
8兵线済来先决模入供定树希逮技鉄多连写塩。着刊禁浩歩人仕设谢争关周徒今高。十育幕桂球门载任快毎社洋着道育纸格幻末。关机高害通方纳狱社州要北相持中表。郎市真提里过何连地更重都山割周。
9-----READING_TIME-----
101
diff --git a/tests/Wallabag/CoreBundle/Tools/samples/cyrillic.txt b/tests/Wallabag/CoreBundle/Tools/samples/cyrillic.txt
index 7b904da4..90906d04 100644
--- a/tests/Wallabag/CoreBundle/Tools/samples/cyrillic.txt
+++ b/tests/Wallabag/CoreBundle/Tools/samples/cyrillic.txt
@@ -1,7 +1,10 @@
1-----CONTENT-----
1Лорем ипсум долор сит амет, ех цум иллуд деленит, пер регионе фацилис те. Еи мел видит саепе интеллегам, яуас маиестатис цонституам яуо ат, цивибус реформиданс нецесситатибус ид яуи. Импетус тациматес пертинах ад еум. Усу еу легере бландит. 2Лорем ипсум долор сит амет, ех цум иллуд деленит, пер регионе фацилис те. Еи мел видит саепе интеллегам, яуас маиестатис цонституам яуо ат, цивибус реформиданс нецесситатибус ид яуи. Импетус тациматес пертинах ад еум. Усу еу легере бландит.
2 3
3Ан меа тритани иуварет, иллум сцаевола легендос ат меа, дебитис импедит нусяуам ест ад. Не маиорум молестие цотидиеяуе вис. Иисяуе цонцлудатуряуе меи еу, татион цонсецтетуер еи про. Либер риденс ид хас, ид цонсул сенсерит пертинациа меа. Фацер молестиае цомпрехенсам ад еум, ин хис апеириан вивендум. Яуи аудире епицуреи иудицабит ат, веро хабео вертерем ад иус. Бонорум плацерат ин вис, сеа но оцурререт принципес интерессет, хас ет дицерет диспутандо. 4Ан меа тритани иуварет, иллум сцаевола легендос ат меа, дебитис импедит нусяуам ест ад. Не маиорум молестие цотидиеяуе вис. Иисяуе цонцлудатуряуе меи еу, татион цонсецтетуер еи про. Либер риденс ид хас, ид цонсул сенсерит пертинациа меа. Фацер молестиае цомпрехенсам ад еум, ин хис апеириан вивендум. Яуи аудире епицуреи иудицабит ат, веро хабео вертерем ад иус. Бонорум плацерат ин вис, сеа но оцурререт принципес интерессет, хас ет дицерет диспутандо.
4 5
5Яуо цу цлита оцурререт. Сонет менандри ин сеа. Еум те нонумы вертерем. Вирис еяуидем фацилиси ет вим, делицата интеллегат иус ин. Ид дицат суммо витае вел, алияуип делецтус те дуо, цу вих хинц дуис видиссе. Нец цу фацилис урбанитас, алиа инсоленс ассуеверит при ут. 6Яуо цу цлита оцурререт. Сонет менандри ин сеа. Еум те нонумы вертерем. Вирис еяуидем фацилиси ет вим, делицата интеллегат иус ин. Ид дицат суммо витае вел, алияуип делецтус те дуо, цу вих хинц дуис видиссе. Нец цу фацилис урбанитас, алиа инсоленс ассуеверит при ут.
6 7
7Яуаеяуе абхорреант инцоррупте не сеа, еу еирмод ерудити вих. Вел оптион тритани цоррумпит те. Поссе сусципит губергрен ут мел, ет еос ириуре менандри еффициенди. Те сале нулла цонсецтетуер сеа, меа не прима алиенум еффициантур. При ет воцибус реформиданс, темпор албуциус сед ан. Еи утрояуе волумус иус, атяуи цонгуе но меи. \ No newline at end of file 8Яуаеяуе абхорреант инцоррупте не сеа, еу еирмод ерудити вих. Вел оптион тритани цоррумпит те. Поссе сусципит губергрен ут мел, ет еос ириуре менандри еффициенди. Те сале нулла цонсецтетуер сеа, меа не прима алиенум еффициантур. При ет воцибус реформиданс, темпор албуциус сед ан. Еи утрояуе волумус иус, атяуи цонгуе но меи.
9-----READING_TIME-----
101
diff --git a/tests/Wallabag/CoreBundle/Tools/samples/greek.txt b/tests/Wallabag/CoreBundle/Tools/samples/greek.txt
index 59f15b8b..f8ade0d7 100644
--- a/tests/Wallabag/CoreBundle/Tools/samples/greek.txt
+++ b/tests/Wallabag/CoreBundle/Tools/samples/greek.txt
@@ -1,3 +1,4 @@
1-----CONTENT-----
1Λορεμ ιπσθμ δολορ σιτ αμετ, ηασ νο θταμθρ qθαεqθε ρεπρεηενδθντ. Ναμ λατινε προμπτα qθαερενδθμ ιδ. Νεc ει φαcερ cονcλθδατθρqθε, vολθπτθα vολθπταρια εφφιcιενδι αδ προ, νε σεα ασσεντιορ δεφινιεβασ. Μεα αγαμ ειθσ δολορε ετ, ηισ ει cορπορα περφεcτο. Vιξ cιβο δελενιτ νε, jθστο ριδενσ οπορτερε σεδ ιδ. 2Λορεμ ιπσθμ δολορ σιτ αμετ, ηασ νο θταμθρ qθαεqθε ρεπρεηενδθντ. Ναμ λατινε προμπτα qθαερενδθμ ιδ. Νεc ει φαcερ cονcλθδατθρqθε, vολθπτθα vολθπταρια εφφιcιενδι αδ προ, νε σεα ασσεντιορ δεφινιεβασ. Μεα αγαμ ειθσ δολορε ετ, ηισ ει cορπορα περφεcτο. Vιξ cιβο δελενιτ νε, jθστο ριδενσ οπορτερε σεδ ιδ.
2 3
3Ηισ νισλ ιθvαρετ γθβεργρεν εξ. Εθμ ιμπεδιτ δετραξιτ ινιμιcθσ ατ, αλια βλανδιτ δθο εα, μεα ιλλθδ επιcθρι cονσετετθρ αδ. Ιλλθδ γραεcε δελενιτι ηισ νο. Νεc ιδ ριδενσ εθισμοδ περιcθλισ, vισ αδ λαβοραμθσ περσεcθτι. Ιθσ εα λθπτατθμ αλιqθανδο δισπθτανδο. 4Ηισ νισλ ιθvαρετ γθβεργρεν εξ. Εθμ ιμπεδιτ δετραξιτ ινιμιcθσ ατ, αλια βλανδιτ δθο εα, μεα ιλλθδ επιcθρι cονσετετθρ αδ. Ιλλθδ γραεcε δελενιτι ηισ νο. Νεc ιδ ριδενσ εθισμοδ περιcθλισ, vισ αδ λαβοραμθσ περσεcθτι. Ιθσ εα λθπτατθμ αλιqθανδο δισπθτανδο.
@@ -6,4 +7,6 @@
6 7
7Cθ σεδ αλβθcιθσ ποστθλαντ. Vιξ ιδ ηομερο περcιπιτ cονcεπταμ. Ιν vιμ λιβρισ vιδερερ, εξ vισ αλιι ερρορ. Vιξ λοβορτισ ασσεντιορ cοντεντιονεσ τε, νε ηασ δεcορε περcιπιτθρ. Εστ εξ δισπθτατιονι δεφινιτιονεμ, qθοδ πηαεδρθμ προ εθ, εξ ηασ ιντεγρε ελιγενδι cονσεcτετθερ. 8Cθ σεδ αλβθcιθσ ποστθλαντ. Vιξ ιδ ηομερο περcιπιτ cονcεπταμ. Ιν vιμ λιβρισ vιδερερ, εξ vισ αλιι ερρορ. Vιξ λοβορτισ ασσεντιορ cοντεντιονεσ τε, νε ηασ δεcορε περcιπιτθρ. Εστ εξ δισπθτατιονι δεφινιτιονεμ, qθοδ πηαεδρθμ προ εθ, εξ ηασ ιντεγρε ελιγενδι cονσεcτετθερ.
8 9
9Ιθσ μολλισ ειρμοδ νο, vιξ νοστρθμ cονσετετθρ ει. Ιθδιcο vερτερεμ λθcιλιθσ qθι τε, νε προμπτα θτροqθε αccομμοδαρε περ. Φαcετε μανδαμθσ ηασ εξ, λιβερ δεβετ εθμ εξ, vιξ ιδ διcερετ σιγνιφερθμqθε. Εθ vιξ vοcεντ. \ No newline at end of file 10Ιθσ μολλισ ειρμοδ νο, vιξ νοστρθμ cονσετετθρ ει. Ιθδιcο vερτερεμ λθcιλιθσ qθι τε, νε προμπτα θτροqθε αccομμοδαρε περ. Φαcετε μανδαμθσ ηασ εξ, λιβερ δεβετ εθμ εξ, vιξ ιδ διcερετ σιγνιφερθμqθε. Εθ vιξ vοcεντ.
11-----READING_TIME-----
121
diff --git a/tests/Wallabag/CoreBundle/Tools/samples/japanese.txt b/tests/Wallabag/CoreBundle/Tools/samples/japanese.txt
new file mode 100644
index 00000000..013a8d74
--- /dev/null
+++ b/tests/Wallabag/CoreBundle/Tools/samples/japanese.txt
@@ -0,0 +1,10 @@
1-----CONTENT-----
2聞7配なク時初かきぴ触整ヨ国鴨覧女ミ将増3部ゅ見荷や言企まげやラ千第ロル企族リた期寄け。戦ト理載コミチヒ芸面だ会入テヒロソ一期ナトヒ試鮮せお天出並ぞる体森ヘツノ決市ね地各ナク強町ず前目とまなを活直オ携握湯りよ。
3
4流ムワ作大禁ヒフ断日ヱ断千ね消諸もとぐろ中勧リ配年リ文7茅ろへりめ辺渡フ三負安ぼ国撮ライム以逃めじット州67棋うきゃ。催キケ者乗フヒソツ染64崎ク捉示よぴふら道世へび属品おく西捕ニレ交重イフ式買散ル展五めづっイ鎧属ざごび数開キハツ聞続表クシタ補球ソウ禁源託ひれも。
5
6季手ッがふ挙思メ勢1使すけねげ日熱争らあふか位義エコ望桑安く決管ーひ広間キヱ皇北ょはこ養山ミ放見負さぞて故携訃畑港ひわン。著支にふみ意豊ラだ球監トクユ馬惨が抱審リヒ労厚ゅぽひ継貸ミノ果疑文キヤ闘府兼ユカシト多不っあ財責エ速訴径猶げすぽ。
7
8了摘見いぶころ会料へゆぱ法利コツハリ統財千りイ伝年りぜ提社ロ片追ごー合作イカシニ感山よち真器敗香レれさ。視シ探大イ令69真ケトヱ便都ケホワナ境号ヱカオハ一助む関念ろんび幼脚要だ客投ヱハイ針教ヒノウラ階担うスりね袖陸ょげけ同講ノ料全ヤ催宮補ゆ徳就画圧愛め。
9-----READING_TIME-----
101
diff --git a/tests/Wallabag/CoreBundle/Tools/samples/korean.txt b/tests/Wallabag/CoreBundle/Tools/samples/korean.txt
new file mode 100644
index 00000000..e3ef2af6
--- /dev/null
+++ b/tests/Wallabag/CoreBundle/Tools/samples/korean.txt
@@ -0,0 +1,10 @@
1-----CONTENT-----
2국군은 국가의 안전보장과 국토방위의 신성한 의무를 수행함을 사명으로 하며, 대통령이 임시회의 집회를 요구할 때에는 기간과 집회요구의 이유를 명시하여야 한다. 정당의 목적이나 활동이 민주적 기본질서에 위배될 때에는 정부는 헌법재판소에 그 해산을 제소할 수 있고. 감사위원은 원장의 제청으로 대통령이 임명하고.
3
4대한민국의 주권은 국민에게 있고, 국회는 국민의 보통·평등·직접·비밀선거에 의하여 선출된 국회의원으로 구성한다. 국가는 농업 및 어업을 보호·육성하기 위하여 농·어촌종합개발과 그 지원등 필요한 계획을 수립·시행하여야 한다. 대통령의 임기연장 또는 중임변경을 위한 헌법개정은 그 헌법개정 제안 당시의 대통령에 대하여는 효력이 없다.
5
6국회가 재적의원 과반수의 찬성으로 계엄의 해제를 요구한 때에는 대통령은 이를 해제하여야 한다, 선거에 관한 경비는 법률이 정하는 경우를 제외하고는 정당 또는 후보자에게 부담시킬 수 없다. 그 정치적 중립성은 준수된다. 헌법개정안은 국회가 의결한 후 30일 이내에 국민투표에 붙여 국회의원선거권자 과반수의 투표와 투표자 과반수의 찬성을 얻어야 한다.
7
8내부규율과 사무처리에 관한 규칙을 제정할 수 있다. 대통령에 대한 탄핵소추는 국회재적의원 과반수의 발의와 국회재적의원 3분의 2 이상의 찬성이 있어야 한다. 대통령은 국가의 원수이며. 대통령이 궐위된 때 또는 대통령 당선자가 사망하거나 판결 기타의 사유로 그 자격을 상실한 때에는 60일 이내에 후임자를 선거한다.
9-----READING_TIME-----
102
diff --git a/tests/Wallabag/CoreBundle/Tools/samples/latin.txt b/tests/Wallabag/CoreBundle/Tools/samples/latin.txt
index 605cc40e..27988597 100644
--- a/tests/Wallabag/CoreBundle/Tools/samples/latin.txt
+++ b/tests/Wallabag/CoreBundle/Tools/samples/latin.txt
@@ -1,3 +1,4 @@
1-----CONTENT-----
1Lorem ipsum dolor sit amet, pro vivendo oporteat pertinacia ei. Vim fabellas molestiae cu, vel nibh legimus ea, in qui atomorum democritum. Ius ne agam soluta ignota, his sale aperiri complectitur te, omnis volumus accusam an eos. Ut mentitum appetere mel, minim temporibus eloquentiam sea ea. 2Lorem ipsum dolor sit amet, pro vivendo oporteat pertinacia ei. Vim fabellas molestiae cu, vel nibh legimus ea, in qui atomorum democritum. Ius ne agam soluta ignota, his sale aperiri complectitur te, omnis volumus accusam an eos. Ut mentitum appetere mel, minim temporibus eloquentiam sea ea.
2 3
3Tation nominati pro ad. Pri eros eloquentiam reformidans ea, et liber epicurei erroribus pro, pri patrioque repudiandae et. Cetero perfecto at eam. Eros hendrerit constituto vix at, brute aperiri adolescens pro eu. Vix lucilius consulatu ei, ullum tantas munere vel in, regione feugiat eligendi at eam. 4Tation nominati pro ad. Pri eros eloquentiam reformidans ea, et liber epicurei erroribus pro, pri patrioque repudiandae et. Cetero perfecto at eam. Eros hendrerit constituto vix at, brute aperiri adolescens pro eu. Vix lucilius consulatu ei, ullum tantas munere vel in, regione feugiat eligendi at eam.
@@ -6,4 +7,6 @@ Eam an lucilius iracundia, audire diceret facilisi his in, ex paulo pertinacia p
6 7
7Nec ut quod probo eligendi, cu dico iriure aperiam vis. Augue causae abhorreant per ut, iriure repudiandae no nam, exerci equidem deleniti nam te. Et duo saperet debitis adipiscing, quo odio audiam no, ex iudico delenit propriae duo. Eu eum eros abhorreant, an tractatos expetendis est. 8Nec ut quod probo eligendi, cu dico iriure aperiam vis. Augue causae abhorreant per ut, iriure repudiandae no nam, exerci equidem deleniti nam te. Et duo saperet debitis adipiscing, quo odio audiam no, ex iudico delenit propriae duo. Eu eum eros abhorreant, an tractatos expetendis est.
8 9
9Vix. \ No newline at end of file 10Vix.
11-----READING_TIME-----
121