Compare commits

..

34 Commits
2.3.5 ... 2.3.6

Author SHA1 Message Date
3f7a28de84 Release wallabag 2.3.6 2019-01-11 17:16:00 +01:00
c17d1ab033 Merge pull request #3835 from wallabag/prepare-2.3.6
Prepare 2.3.6 release
2019-01-11 17:12:13 +01:00
3bed2e440e Prepare 2.3.6 release 2019-01-11 14:16:41 +01:00
3625833b2c Merge pull request #3826 from wallabag/epub-toc
Rework of EPUB/PDF exports
2019-01-11 13:34:38 +01:00
03663530ed Merge pull request #3831 from wallabag/fix/api-bad-client-id
Cast client id to avoid PG error
2019-01-10 17:03:03 +01:00
ca990600da Merge pull request #3833 from techexo/patch-1
Fix settings field inverted
2019-01-10 10:02:34 +01:00
bb8ad42b27 Update entries.html.twig
Should fix https://github.com/wallabag/wallabag/issues/3832
2019-01-10 04:25:51 +01:00
d4466a37fe Update entries.html.twig
Should fix https://github.com/wallabag/wallabag/issues/3832
2019-01-10 04:23:08 +01:00
3a2d4cf9fd Cast client id to avoid PG error
If someone send a malformated client_id when trying to authenticate using the API we got a 500 if wallabag use postgres because the request send a string instead of an integer.
2019-01-09 23:31:14 +01:00
5e1f27767b EntriesExport: avoid else on $authors
Signed-off-by: Kevin Decherf <kevin@kdecherf.com>
2019-01-09 16:26:19 +01:00
dac93644e8 EntriesExport: sanitize filename and fix tests
Filename will now only use a-zA-Z0-9-' and space.

Fixes remaining filename issue on #3811

Signed-off-by: Kevin Decherf <kevin@kdecherf.com>
2019-01-08 15:13:35 +01:00
ad5ef8bca0 EntriesExport/pdf: move notice to the end, add metadata cover
Signed-off-by: Kevin Decherf <kevin@kdecherf.com>
2019-01-07 23:36:41 +01:00
af83d05ce2 Add translations
Signed-off-by: Kevin Decherf <kevin@kdecherf.com>
2019-01-07 23:06:02 +01:00
4944703edc EntriesExport/epub: add metadata to each entry's cover
Add metadata to the cover of each entry:

- Publishers
- Estimated reading time
- Date of creation ("Added on")
- Address (URL)

Related to #2821

Signed-off-by: Kevin Decherf <kevin@kdecherf.com>
2019-01-07 21:44:14 +01:00
f810834623 EntriesExport: change authors and title when not single entry export
Change '{method} authors' (which gives 'Tag_entries authors' when
exporting a tag) to 'Various authors'.

When exporting a tag (tag_entries), change the title from 'Tag_entries
articles' to 'Tag {tag} articles'.

Signed-off-by: Kevin Decherf <kevin@kdecherf.com>
2019-01-07 21:44:14 +01:00
30cf72bf55 EntriesExport/epub: revert c779373f, move exportinfo to the end of the book
Signed-off-by: Kevin Decherf <kevin@kdecherf.com>
2019-01-07 21:43:16 +01:00
edd1825b58 EntriesExport/epub: use sha1 sums for filenames, fix and rename title chapters
This commit renames entry chapters file using a sha1 sum of their title
for simplicity. Also we fix the 'Title' chapter duplicate issue by using
the hash of the related entry and the suffix '_title'.

Signed-off-by: Kevin Decherf <kevin@kdecherf.com>
2019-01-07 21:41:12 +01:00
063d5e7bda EntriesExport/epub: remove TOC page
This change only remove the rendered page of the TOC at the end of the
book, the TOC remains available to readers.

Fixes #3603

Signed-off-by: Kevin Decherf <kevin@kdecherf.com>
2019-01-07 21:11:05 +01:00
5de17117a1 Merge pull request #3827 from wallabag/epub-quote
EntriesExport/epub: replace epub identifier with unique urn
2019-01-07 11:59:38 +01:00
d2aec7096d Merge pull request #3820 from lizyn/bugfix/incorrect-calculation-of-CJK-characters-in-reading-time-estimation
Fix incorrect reading time calculation for entries with CJK characters
2019-01-07 10:17:29 +01:00
bf22266a62 EntriesExport/epub: replace epub identifier with unique urn
We replace the title used as the unique identifier of the epub file with
a urn following the format:

  urn:wallabag:{sha1("wallabagUrl:listOfEntryIdsSeparatedByComma")}

This format is repeatable: it always gives the same uid for the same
list of entries.

Fixes #3811

Signed-off-by: Kevin Decherf <kevin@kdecherf.com>
2019-01-06 23:29:32 +01:00
7f8630b91c Counting two characters together as a word in CJK 2019-01-06 01:21:13 +08:00
35983eb9bb Improve reading time tests 2019-01-04 11:23:33 +01:00
8f5c4b083c Merge pull request #3816 from wallabag/validate-import-entry
Validate imported entry to avoid error on import
2019-01-04 11:06:53 +01:00
9f8f188d92 Validate imported entry to avoid error on import
We got some imports with a missing `url` field generating some errors while trying to retrieve an existing entry with that url.
Introducing the `validateEntry` allow us to dismiss a message when it doesn't have an url (or other missing stuff in the future)
2019-01-03 09:42:06 +01:00
2378fd6347 Merge pull request #3823 from wallabag/fix-tag-api-leak
Fix tag API leak
2019-01-03 09:14:26 +01:00
6c40d7fc85 TagRestController: fix test for tag without entries
As the deletion now requires that at least one entry for the user must
be linked to the given tag, we fix the test testDeleteUserTag by linking
it to an entry.

Signed-off-by: Kevin Decherf <kevin@kdecherf.com>
2018-12-30 01:34:49 +01:00
2a0e0a47d8 TagRestController: rewrite delete actions to only retrieve tags related to the user
Fixes #3815

Signed-off-by: Kevin Decherf <kevin@kdecherf.com>
2018-12-30 01:34:49 +01:00
0ee9848231 TagRestController: add tests to ensure that other user's tags are unreachable
Signed-off-by: Kevin Decherf <kevin@kdecherf.com>
2018-12-30 01:34:49 +01:00
6708bf238d TagRepository: refactor query builder for queries by userId
Signed-off-by: Kevin Decherf <kevin@kdecherf.com>
2018-12-30 01:34:44 +01:00
bafb9744c8 fixtures: refactor EntryData, TagData, add a new tag
Signed-off-by: Kevin Decherf <kevin@kdecherf.com>
2018-12-29 19:22:05 +01:00
5becf260fa fix incorrect reading time calculation for entries with CJK characters 2018-12-25 15:31:44 +08:00
4d0c632c70 Merge pull request #3814 from wallabag/2.3.6-dev
Jump to 2.3.6-dev and update release process
2018-12-17 09:34:15 +01:00
4fd5f670fe Jump to 2.3.6-dev and update release process
Fix release archive in `release.sh` and also fix a typo in the release process
2018-12-15 08:14:47 +01:00
51 changed files with 653 additions and 243 deletions

View File

@ -1,5 +1,19 @@
# Changelog
## [2.3.6](https://github.com/wallabag/wallabag/tree/2.3.6)
[Full Changelog](https://github.com/wallabag/wallabag/compare/2.3.5...2.3.6)
### Fixes
- Jump to 2.3.6-dev and update release process [#3814](https://github.com/wallabag/wallabag/pull/3814)
- Fix tag API leak [#3823](https://github.com/wallabag/wallabag/pull/3823)
- Validate imported entry to avoid error on import [#3816](https://github.com/wallabag/wallabag/pull/3816)
- Fix incorrect reading time calculation for entries with CJK characters [#3820](https://github.com/wallabag/wallabag/pull/3820)
- EntriesExport/epub: replace epub identifier with unique urn [#3827](https://github.com/wallabag/wallabag/pull/3827)
- Fix settings field inverted [#3833](https://github.com/wallabag/wallabag/pull/3833)
- Cast client id to avoid PG error [#3831](https://github.com/wallabag/wallabag/pull/3831)
- Rework of EPUB/PDF exports [#3826](https://github.com/wallabag/wallabag/pull/3826)
## [2.3.5](https://github.com/wallabag/wallabag/tree/2.3.5)
[Full Changelog](https://github.com/wallabag/wallabag/compare/2.3.4...2.3.5)

View File

@ -53,7 +53,7 @@ make release VERSION=$LAST_WALLABAG_RELEASE
- Update the URL shortener (used on `wllbg.org` to generate links like `https://wllbg.org/latest-v2-package` or `http://wllbg.org/latest-v2`)
- Update Dockerfile https://github.com/wallabag/docker (and create a new tag)
- Update wallabag.org website (downloads, MD5 sum, releases and new blog post)
- Put the next patch version suffixed with `-dev` in `app/config/config.yml` (`wallabag_core.version`)
- Put the next patch version suffixed with `-dev` in `app/config/wallabag.yml` (`wallabag_core.version`)
- Drink a :beer:!
### `composer.lock`

View File

@ -1,5 +1,5 @@
wallabag_core:
version: 2.3.5
version: 2.3.6
paypal_url: "https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=9UBA65LG3FX9Y&lc=gb"
languages:
en: 'English'

100
composer.lock generated
View File

@ -631,16 +631,16 @@
},
{
"name": "doctrine/doctrine-bundle",
"version": "1.10.0",
"version": "1.10.1",
"source": {
"type": "git",
"url": "https://github.com/doctrine/DoctrineBundle.git",
"reference": "82d2c63cd09acbde2332f55d9aa7b28aefe4983d"
"reference": "98551d71f515692c2278073e0d483763ac70b341"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/doctrine/DoctrineBundle/zipball/82d2c63cd09acbde2332f55d9aa7b28aefe4983d",
"reference": "82d2c63cd09acbde2332f55d9aa7b28aefe4983d",
"url": "https://api.github.com/repos/doctrine/DoctrineBundle/zipball/98551d71f515692c2278073e0d483763ac70b341",
"reference": "98551d71f515692c2278073e0d483763ac70b341",
"shasum": ""
},
"require": {
@ -712,7 +712,7 @@
"orm",
"persistence"
],
"time": "2018-11-30T13:53:17+00:00"
"time": "2019-01-07T15:31:08+00:00"
},
{
"name": "doctrine/doctrine-cache-bundle",
@ -1120,12 +1120,12 @@
"version": "v2.5.14",
"source": {
"type": "git",
"url": "https://github.com/doctrine/doctrine2.git",
"url": "https://github.com/doctrine/orm.git",
"reference": "810a7baf81462a5ddf10e8baa8cb94b6eec02754"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/doctrine/doctrine2/zipball/810a7baf81462a5ddf10e8baa8cb94b6eec02754",
"url": "https://api.github.com/repos/doctrine/orm/zipball/810a7baf81462a5ddf10e8baa8cb94b6eec02754",
"reference": "810a7baf81462a5ddf10e8baa8cb94b6eec02754",
"shasum": ""
},
@ -3125,16 +3125,16 @@
},
{
"name": "j0k3r/graby",
"version": "1.15.5",
"version": "1.18.0",
"source": {
"type": "git",
"url": "https://github.com/j0k3r/graby.git",
"reference": "b3dfd7b2eb08686182885b49dabc41795ca07346"
"reference": "7ac1e2d696744c1f7c9c76658677efda33df020a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/j0k3r/graby/zipball/b3dfd7b2eb08686182885b49dabc41795ca07346",
"reference": "b3dfd7b2eb08686182885b49dabc41795ca07346",
"url": "https://api.github.com/repos/j0k3r/graby/zipball/7ac1e2d696744c1f7c9c76658677efda33df020a",
"reference": "7ac1e2d696744c1f7c9c76658677efda33df020a",
"shasum": ""
},
"require": {
@ -3180,20 +3180,20 @@
}
],
"description": "Graby helps you extract article content from web pages",
"time": "2018-12-04T15:05:28+00:00"
"time": "2019-01-08T09:12:43+00:00"
},
{
"name": "j0k3r/graby-site-config",
"version": "1.0.69",
"version": "1.0.72",
"source": {
"type": "git",
"url": "https://github.com/j0k3r/graby-site-config.git",
"reference": "a271df62909bc9e716b95a457d3c95b1a42f1d01"
"reference": "b76b230cf044e134574de7cd7af09d6b01ab0f15"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/j0k3r/graby-site-config/zipball/a271df62909bc9e716b95a457d3c95b1a42f1d01",
"reference": "a271df62909bc9e716b95a457d3c95b1a42f1d01",
"url": "https://api.github.com/repos/j0k3r/graby-site-config/zipball/b76b230cf044e134574de7cd7af09d6b01ab0f15",
"reference": "b76b230cf044e134574de7cd7af09d6b01ab0f15",
"shasum": ""
},
"require": {
@ -3220,7 +3220,7 @@
}
],
"description": "Graby site config files",
"time": "2018-12-10T07:01:08+00:00"
"time": "2019-01-11T12:55:48+00:00"
},
{
"name": "j0k3r/php-readability",
@ -4484,16 +4484,16 @@
},
{
"name": "paragonie/random_compat",
"version": "v2.0.17",
"version": "v2.0.18",
"source": {
"type": "git",
"url": "https://github.com/paragonie/random_compat.git",
"reference": "29af24f25bab834fcbb38ad2a69fa93b867e070d"
"reference": "0a58ef6e3146256cc3dc7cc393927bcc7d1b72db"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/paragonie/random_compat/zipball/29af24f25bab834fcbb38ad2a69fa93b867e070d",
"reference": "29af24f25bab834fcbb38ad2a69fa93b867e070d",
"url": "https://api.github.com/repos/paragonie/random_compat/zipball/0a58ef6e3146256cc3dc7cc393927bcc7d1b72db",
"reference": "0a58ef6e3146256cc3dc7cc393927bcc7d1b72db",
"shasum": ""
},
"require": {
@ -4529,7 +4529,7 @@
"pseudorandom",
"random"
],
"time": "2018-07-04T16:31:37+00:00"
"time": "2019-01-03T20:59:08+00:00"
},
{
"name": "php-amqplib/php-amqplib",
@ -5125,16 +5125,16 @@
},
{
"name": "react/promise",
"version": "v2.7.0",
"version": "v2.7.1",
"source": {
"type": "git",
"url": "https://github.com/reactphp/promise.git",
"reference": "f4edc2581617431aea50430749db55cc3fc031b3"
"reference": "31ffa96f8d2ed0341a57848cbb84d88b89dd664d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/reactphp/promise/zipball/f4edc2581617431aea50430749db55cc3fc031b3",
"reference": "f4edc2581617431aea50430749db55cc3fc031b3",
"url": "https://api.github.com/repos/reactphp/promise/zipball/31ffa96f8d2ed0341a57848cbb84d88b89dd664d",
"reference": "31ffa96f8d2ed0341a57848cbb84d88b89dd664d",
"shasum": ""
},
"require": {
@ -5167,7 +5167,7 @@
"promise",
"promises"
],
"time": "2018-06-13T15:59:06+00:00"
"time": "2019-01-07T21:25:54+00:00"
},
{
"name": "scheb/two-factor-bundle",
@ -5359,16 +5359,16 @@
},
{
"name": "sensiolabs/security-checker",
"version": "v5.0.2",
"version": "v5.0.3",
"source": {
"type": "git",
"url": "https://github.com/sensiolabs/security-checker.git",
"reference": "728f9fb0fe815003b3bcfd331d33106c0d8a6b1e"
"reference": "46be3f58adac13084497961e10eed9a7fb4d44d1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sensiolabs/security-checker/zipball/728f9fb0fe815003b3bcfd331d33106c0d8a6b1e",
"reference": "728f9fb0fe815003b3bcfd331d33106c0d8a6b1e",
"url": "https://api.github.com/repos/sensiolabs/security-checker/zipball/46be3f58adac13084497961e10eed9a7fb4d44d1",
"reference": "46be3f58adac13084497961e10eed9a7fb4d44d1",
"shasum": ""
},
"require": {
@ -5401,7 +5401,7 @@
}
],
"description": "A security checker for your composer.lock",
"time": "2018-12-10T06:08:43+00:00"
"time": "2018-12-19T17:14:59+00:00"
},
{
"name": "simplepie/simplepie",
@ -6472,16 +6472,16 @@
},
{
"name": "twig/twig",
"version": "v1.35.4",
"version": "v1.36.0",
"source": {
"type": "git",
"url": "https://github.com/twigphp/Twig.git",
"reference": "7e081e98378a1e78c29cc9eba4aefa5d78a05d2a"
"reference": "730c9c4471b5152d23061feb02b03382264c8a15"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/twigphp/Twig/zipball/7e081e98378a1e78c29cc9eba4aefa5d78a05d2a",
"reference": "7e081e98378a1e78c29cc9eba4aefa5d78a05d2a",
"url": "https://api.github.com/repos/twigphp/Twig/zipball/730c9c4471b5152d23061feb02b03382264c8a15",
"reference": "730c9c4471b5152d23061feb02b03382264c8a15",
"shasum": ""
},
"require": {
@ -6491,12 +6491,12 @@
"require-dev": {
"psr/container": "^1.0",
"symfony/debug": "^2.7",
"symfony/phpunit-bridge": "^3.3"
"symfony/phpunit-bridge": "^3.4.19|^4.1.8"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.35-dev"
"dev-master": "1.36-dev"
}
},
"autoload": {
@ -6534,7 +6534,7 @@
"keywords": [
"templating"
],
"time": "2018-07-13T07:12:17+00:00"
"time": "2018-12-16T10:34:11+00:00"
},
{
"name": "wallabag/php-mobi",
@ -6656,16 +6656,16 @@
},
{
"name": "white-october/pagerfanta-bundle",
"version": "v1.2.2",
"version": "v1.2.3",
"source": {
"type": "git",
"url": "https://github.com/whiteoctober/WhiteOctoberPagerfantaBundle.git",
"reference": "7aa8d797c46d46a3e950fbfa7e78de854658cb7b"
"reference": "3a2f39fecde9b8aefe7c9cbd0baf06bc72ef3e5e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/whiteoctober/WhiteOctoberPagerfantaBundle/zipball/7aa8d797c46d46a3e950fbfa7e78de854658cb7b",
"reference": "7aa8d797c46d46a3e950fbfa7e78de854658cb7b",
"url": "https://api.github.com/repos/whiteoctober/WhiteOctoberPagerfantaBundle/zipball/3a2f39fecde9b8aefe7c9cbd0baf06bc72ef3e5e",
"reference": "3a2f39fecde9b8aefe7c9cbd0baf06bc72ef3e5e",
"shasum": ""
},
"require": {
@ -6710,7 +6710,7 @@
"page",
"paging"
],
"time": "2018-10-02T13:23:39+00:00"
"time": "2018-12-05T16:58:18+00:00"
},
{
"name": "willdurand/hateoas",
@ -6769,7 +6769,7 @@
"email": "adrien.brault@gmail.com"
},
{
"name": "William Durand",
"name": "William DURAND",
"email": "william.durand1@gmail.com"
}
],
@ -6819,7 +6819,7 @@
],
"authors": [
{
"name": "William Durand",
"name": "William DURAND",
"email": "william.durand1@gmail.com"
}
],
@ -7561,12 +7561,12 @@
"source": {
"type": "git",
"url": "https://github.com/symfony/phpunit-bridge.git",
"reference": "2155067dfc73e0e77dbc26f236af17e4df552de5"
"reference": "5dab0d4b2ac99ab22b447b615fdfdc10ec4af3d5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/2155067dfc73e0e77dbc26f236af17e4df552de5",
"reference": "2155067dfc73e0e77dbc26f236af17e4df552de5",
"url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/5dab0d4b2ac99ab22b447b615fdfdc10ec4af3d5",
"reference": "5dab0d4b2ac99ab22b447b615fdfdc10ec4af3d5",
"shasum": ""
},
"require": {
@ -7619,7 +7619,7 @@
],
"description": "Symfony PHPUnit Bridge",
"homepage": "https://symfony.com",
"time": "2018-11-20T16:47:12+00:00"
"time": "2019-01-01T13:45:19+00:00"
},
{
"name": "symfony/polyfill-php72",

View File

@ -15,6 +15,6 @@ cd $TMP_FOLDER/$RELEASE_FOLDER/$VERSION && php bin/console wallabag:install --en
cd $TMP_FOLDER/$RELEASE_FOLDER/$VERSION && php bin/console assets:install --env=$ENV --symlink --relative
cd $TMP_FOLDER/$RELEASE_FOLDER && tar czf wallabag-$VERSION.tar.gz --exclude="var/cache/*" --exclude="var/logs/*" --exclude="var/sessions/*" --exclude=".git" $VERSION
echo "MD5 checksum of the package for wallabag $VERSION"
md5 $TMP_FOLDER/$RELEASE_FOLDER/wallabag-release-$VERSION.tar.gz
md5 $TMP_FOLDER/$RELEASE_FOLDER/wallabag-$VERSION.tar.gz
echo "Package to upload to the release server:"
echo $TMP_FOLDER/$RELEASE_FOLDER/wallabag-release-$VERSION.tar.gz
echo $TMP_FOLDER/$RELEASE_FOLDER/wallabag-$VERSION.tar.gz

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

@ -14,97 +14,112 @@ class LoadEntryData extends AbstractFixture implements OrderedFixtureInterface
*/
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->setArchived(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

@ -14,33 +14,22 @@ class LoadTagData extends AbstractFixture implements OrderedFixtureInterface
*/
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

@ -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'
@ -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: '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'
@ -402,6 +407,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'
@ -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: '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'
@ -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: '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: 'درباره'
@ -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: 'درون‌ریزی'

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"
@ -402,6 +407,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'
@ -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: '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'
@ -402,6 +407,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'
@ -401,7 +406,8 @@ tag:
placeholder: 'Możesz dodać kilka tagów, oddzielając je przecinkami.'
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'
@ -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: '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'
@ -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: '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: 'О'
@ -388,6 +393,10 @@ tag:
add: 'Добавить'
placeholder: 'Вы можете добавить несколько тегов, разделенных запятой.'
# 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: 'เกี่ยวกับ'
@ -400,6 +405,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'
@ -400,6 +405,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

@ -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

@ -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

@ -56,6 +56,20 @@ class DeveloperControllerTest extends WallabagCoreTestCase
$this->assertArrayHasKey('refresh_token', $data);
}
public function testCreateTokenWithBadClientId()
{
$client = $this->getClient();
$client->request('POST', '/oauth/v2/token', [
'grant_type' => 'password',
'client_id' => '$WALLABAG_CLIENT_ID',
'client_secret' => 'secret',
'username' => 'admin',
'password' => 'mypassword',
]);
$this->assertSame(400, $client->getResponse()->getStatusCode());
}
public function testListingClient()
{
$this->logInAs('admin');

View File

@ -7,6 +7,8 @@ use Wallabag\CoreBundle\Entity\Tag;
class TagRestControllerTest extends WallabagApiTestCase
{
private $otherUserTagLabel = 'bob';
public function testGetUserTags()
{
$this->client->request('GET', '/api/tags.json');
@ -19,17 +21,33 @@ class TagRestControllerTest extends WallabagApiTestCase
$this->assertArrayHasKey('id', $content[0]);
$this->assertArrayHasKey('label', $content[0]);
$tagLabels = array_map(function ($i) {
return $i['label'];
}, $content);
$this->assertNotContains($this->otherUserTagLabel, $tagLabels, 'There is a possible tag leak');
return end($content);
}
public function testDeleteUserTag()
{
$em = $this->client->getContainer()->get('doctrine.orm.entity_manager');
$entry = $this->client->getContainer()
->get('doctrine.orm.entity_manager')
->getRepository('WallabagCoreBundle:Entry')
->findOneWithTags($this->user->getId());
$entry = $entry[0];
$tagLabel = 'tagtest';
$tag = new Tag();
$tag->setLabel($tagLabel);
$em = $this->client->getContainer()->get('doctrine.orm.entity_manager');
$em->persist($tag);
$entry->addTag($tag);
$em->persist($entry);
$em->flush();
$em->clear();
@ -53,6 +71,16 @@ class TagRestControllerTest extends WallabagApiTestCase
$this->assertNull($tag, $tagLabel . ' was removed because it begun an orphan tag');
}
public function testDeleteOtherUserTag()
{
$em = $this->client->getContainer()->get('doctrine.orm.entity_manager');
$tag = $em->getRepository('WallabagCoreBundle:Tag')->findOneByLabel($this->otherUserTagLabel);
$this->client->request('DELETE', '/api/tags/' . $tag->getId() . '.json');
$this->assertSame(404, $this->client->getResponse()->getStatusCode());
}
public function dataForDeletingTagByLabel()
{
return [
@ -112,6 +140,13 @@ class TagRestControllerTest extends WallabagApiTestCase
$this->assertSame(404, $this->client->getResponse()->getStatusCode());
}
public function testDeleteTagByLabelOtherUser()
{
$this->client->request('DELETE', '/api/tag/label.json', ['tag' => $this->otherUserTagLabel]);
$this->assertSame(404, $this->client->getResponse()->getStatusCode());
}
/**
* @dataProvider dataForDeletingTagByLabel
*/
@ -180,4 +215,11 @@ class TagRestControllerTest extends WallabagApiTestCase
$this->assertSame(404, $this->client->getResponse()->getStatusCode());
}
public function testDeleteTagsByLabelOtherUser()
{
$this->client->request('DELETE', '/api/tags/label.json', ['tags' => $this->otherUserTagLabel]);
$this->assertSame(404, $this->client->getResponse()->getStatusCode());
}
}

View File

@ -98,7 +98,7 @@ class ExportControllerTest extends WallabagCoreTestCase
$headers = $client->getResponse()->headers;
$this->assertSame('application/x-mobipocket-ebook', $headers->get('content-type'));
$this->assertSame('attachment; filename="' . preg_replace('/[^A-Za-z0-9\-]/', '', $content->getTitle()) . '.mobi"', $headers->get('content-disposition'));
$this->assertSame('attachment; filename="' . $this->getSanitizedFilename($content->getTitle()) . '.mobi"', $headers->get('content-disposition'));
$this->assertSame('binary', $headers->get('content-transfer-encoding'));
}
@ -126,7 +126,7 @@ class ExportControllerTest extends WallabagCoreTestCase
$headers = $client->getResponse()->headers;
$this->assertSame('application/pdf', $headers->get('content-type'));
$this->assertSame('attachment; filename="Tag_entries articles.pdf"', $headers->get('content-disposition'));
$this->assertSame('attachment; filename="Tag foo bar articles.pdf"', $headers->get('content-disposition'));
$this->assertSame('binary', $headers->get('content-transfer-encoding'));
}
@ -212,7 +212,7 @@ class ExportControllerTest extends WallabagCoreTestCase
$headers = $client->getResponse()->headers;
$this->assertSame('application/json', $headers->get('content-type'));
$this->assertSame('attachment; filename="' . $contentInDB->getTitle() . '.json"', $headers->get('content-disposition'));
$this->assertSame('attachment; filename="' . $this->getSanitizedFilename($contentInDB->getTitle()) . '.json"', $headers->get('content-disposition'));
$this->assertSame('UTF-8', $headers->get('content-transfer-encoding'));
$content = json_decode($client->getResponse()->getContent(), true);
@ -281,4 +281,9 @@ class ExportControllerTest extends WallabagCoreTestCase
$this->assertNotEmpty('created_at', (string) $content->entry[0]->created_at);
$this->assertNotEmpty('updated_at', (string) $content->entry[0]->updated_at);
}
private function getSanitizedFilename($title)
{
return preg_replace('/[^A-Za-z0-9\- \']/', '', iconv('utf-8', 'us-ascii//TRANSLIT', $title));
}
}

View File

@ -11,9 +11,9 @@ class UtilsTest extends TestCase
/**
* @dataProvider examples
*/
public function testCorrectWordsCountForDifferentLanguages($text, $expectedCount)
public function testCorrectWordsCountForDifferentLanguages($filename, $text, $expectedCount)
{
static::assertSame((float) $expectedCount, Utils::getReadingTime($text));
static::assertSame((float) $expectedCount, Utils::getReadingTime($text), 'Reading time for: ' . $filename);
}
public function examples()
@ -21,7 +21,17 @@ class UtilsTest extends TestCase
$examples = [];
$finder = (new Finder())->in(__DIR__ . '/samples');
foreach ($finder->getIterator() as $file) {
$examples[] = [$file->getContents(), 1];
preg_match('/-----CONTENT-----\s*(.*?)\s*-----READING_TIME-----\s*(.*)/sx', $file->getContents(), $match);
if (3 !== \count($match)) {
throw new \Exception('Sample file "' . $file->getRelativePathname() . '" as wrong definition, see README.');
}
$examples[] = [
$file->getRelativePathname(),
$match[1], // content
$match[2], // reading time
];
}
return $examples;

View File

@ -0,0 +1,5 @@
Defined language sample should use the following structure:
-----CONTENT-----
-----READING_TIME-----

View File

@ -0,0 +1,10 @@
-----CONTENT-----
职然问讲念谷月挂大报住本読能录要褐込。料士纸木陈与兴组静终図问有。今観深车相环学俳健越増职県県多券报。雪月批导掲稿家缝城间真中崩図人连。前担写治芸面毎作似水州稿注球戦頃。済方宮安目垣強入料会先呼略。計定設負財作覧経己員事田事球岡示差学。最院書模婚金回禁朝船教任分禁検理慮宿。
変送调指式真気交现上様女限宅复。禁业稿者普视想来木残止者済断式安。万致相领鉄再改界逮由竹式元最台変。済问活助库脳部风政京転说区変。文図化仙政常地里芸上褒前読望误记温政信土。惑育候当人万部逮重申結標番業望般。断瀬後社天打日資交献秀世覧第。補当編里身社記利件部夜中心掲大。
时大栗夜测署市要纯京挙化済负品。天最场情算掲放故手茨指岛然渡活民年。第纯交一特问明室试賛际者建。论铜所常縄一広気特秋提公茶可満编旅相変権。
兵线済来先决模入供定树希逮技鉄多连写塩。着刊禁浩歩人仕设谢争关周徒今高。十育幕桂球门载任快毎社洋着道育纸格幻末。关机高害通方纳狱社州要北相持中表。郎市真提里过何连地更重都山割周。
-----READING_TIME-----
1

View File

@ -1,7 +1,10 @@
-----CONTENT-----
Лорем ипсум долор сит амет, ех цум иллуд деленит, пер регионе фацилис те. Еи мел видит саепе интеллегам, яуас маиестатис цонституам яуо ат, цивибус реформиданс нецесситатибус ид яуи. Импетус тациматес пертинах ад еум. Усу еу легере бландит.
Ан меа тритани иуварет, иллум сцаевола легендос ат меа, дебитис импедит нусяуам ест ад. Не маиорум молестие цотидиеяуе вис. Иисяуе цонцлудатуряуе меи еу, татион цонсецтетуер еи про. Либер риденс ид хас, ид цонсул сенсерит пертинациа меа. Фацер молестиае цомпрехенсам ад еум, ин хис апеириан вивендум. Яуи аудире епицуреи иудицабит ат, веро хабео вертерем ад иус. Бонорум плацерат ин вис, сеа но оцурререт принципес интерессет, хас ет дицерет диспутандо.
Яуо цу цлита оцурререт. Сонет менандри ин сеа. Еум те нонумы вертерем. Вирис еяуидем фацилиси ет вим, делицата интеллегат иус ин. Ид дицат суммо витае вел, алияуип делецтус те дуо, цу вих хинц дуис видиссе. Нец цу фацилис урбанитас, алиа инсоленс ассуеверит при ут.
Яуаеяуе абхорреант инцоррупте не сеа, еу еирмод ерудити вих. Вел оптион тритани цоррумпит те. Поссе сусципит губергрен ут мел, ет еос ириуре менандри еффициенди. Те сале нулла цонсецтетуер сеа, меа не прима алиенум еффициантур. При ет воцибус реформиданс, темпор албуциус сед ан. Еи утрояуе волумус иус, атяуи цонгуе но меи.
Яуаеяуе абхорреант инцоррупте не сеа, еу еирмод ерудити вих. Вел оптион тритани цоррумпит те. Поссе сусципит губергрен ут мел, ет еос ириуре менандри еффициенди. Те сале нулла цонсецтетуер сеа, меа не прима алиенум еффициантур. При ет воцибус реформиданс, темпор албуциус сед ан. Еи утрояуе волумус иус, атяуи цонгуе но меи.
-----READING_TIME-----
1

View File

@ -1,3 +1,4 @@
-----CONTENT-----
Λορεμ ιπσθμ δολορ σιτ αμετ, ηασ νο θταμθρ qθαεqθε ρεπρεηενδθντ. Ναμ λατινε προμπτα qθαερενδθμ ιδ. Νεc ει φαρ cονcλθδατθρqθε, vολθπτθα vολθπταρια εφφιcιενδι αδ προ, νε σεα ασσεντιορ δεφινιεβασ. Μεα αγαμ ειθσ δολορε ετ, ηισ ει cορπορα περφεcτο. Vιξ cιβο δελενιτ νε, jθστο ριδενσ οπορτερε σεδ ιδ.
Ηισ νισλ ιθvαρετ γθβεργρεν εξ. Εθμ ιμπεδιτ δετραξιτ ινιμισ ατ, αλια βλανδιτ δθο εα, μεα ιλλθδ επιρι cονσετετθρ αδ. Ιλλθδ γραεcε δελενιτι ηισ νο. Νεc ιδ ριδενσ εθισμοδ περιcθλισ, vισ αδ λαβοραμθσ περσεcθτι. Ιθσ εα λθπτατθμ αλιανδο δισπθτανδο.
@ -6,4 +7,6 @@
Cθ σεδ αλβθcιθσ ποστθλαντ. Vιξ ιδ ηομερο περcιπιτ cονcεπταμ. Ιν vιμ λιβρισ vιδερερ, εξ vισ αλιι ερρορ. Vιξ λοβορτισ ασσεντιορ cοντεντιονεσ τε, νε ηασ δεcορε περcιπιτθρ. Εστ εξ δισπθτατιονι δεφινιτιονεμ, qθοδ πηαεδρθμ προ εθ, εξ ηασ ιντεγρε ελιγενδι cονσεcτετθερ.
Ιθσ μολλισ ειρμοδ νο, vιξ νοστρθμ cονσετετθρ ει. Ιθδιcορτερεμ λθcιλιθσι τε, νε προμπτα θτροqθε αccομμοδαρε περ. Φαcετε μανδαμθσ ηασ εξ, λιβερ δεβετ εθμ εξ, vιξ ιδ διρετ σιγνιφερθμqθε. Εθ vιξ vοντ.
Ιθσ μολλισ ειρμοδ νο, vιξ νοστρθμ cονσετετθρ ει. Ιθδιcορτερεμ λθcιλιθσι τε, νε προμπτα θτροqθε αccομμοδαρε περ. Φαcετε μανδαμθσ ηασ εξ, λιβερ δεβετ εθμ εξ, vιξ ιδ διρετ σιγνιφερθμqθε. Εθ vιξ vοντ.
-----READING_TIME-----
1

View File

@ -0,0 +1,10 @@
-----CONTENT-----
聞7配なク時初かきぴ触整ヨ国鴨覧女ミ将増3部ゅ見荷や言企まげやラ千第ロル企族リた期寄け。戦ト理載コミチヒ芸面だ会入テヒロソ一期ナトヒ試鮮せお天出並ぞる体森ヘツ決市ね地各ナク強町ず前目とまなを活直オ携握湯りよ。
流ムワ作大禁ヒフ断日ヱ断千ね消諸もとぐろ中勧リ配年リ文7茅ろへりめ辺渡フ三負安ぼ国撮ライム以逃めじット州67棋うきゃ。催キケ者乗フヒソツ染64崎ク捉示よぴふら道世へび属品おく西捕ニレ交重イフ式買散ル展五めづっイ鎧属ざごび数開キハツ聞続表クシタ補球ソウ禁源託ひれも。
季手ッがふ挙思メ勢1使すけねげ日熱争らあふか位義エコ望桑安く決管ーひ広間キヱ皇北ょはこ養山ミ放見負さぞて故携訃畑港ひわン。著支にふみ意豊ラだ球監トクユ馬惨が抱審リヒ労厚ゅぽひ継貸ミ果疑文キヤ闘府兼ユカシト多不っあ財責エ速訴径猶げすぽ。
了摘見いぶころ会料へゆぱ法利コツハリ統財千りイ伝年りぜ提社ロ片追ごー合作イカシニ感山よち真器敗香レれさ。視シ探大イ令69真ケトヱ便都ケホワナ境号ヱカオハ一助む関念ろんび幼脚要だ客投ヱハイ針教ヒウラ階担うスりね袖陸ょげけ同講料全ヤ催宮補ゆ徳就画圧愛め。
-----READING_TIME-----
1

View File

@ -0,0 +1,10 @@
-----CONTENT-----
국군은 국가의 안전보장과 국토방위의 신성한 의무를 수행함을 사명으로 하며, 대통령이 임시회의 집회를 요구할 때에는 기간과 집회요구의 이유를 명시하여야 한다. 정당의 목적이나 활동이 민주적 기본질서에 위배될 때에는 정부는 헌법재판소에 그 해산을 제소할 수 있고. 감사위원은 원장의 제청으로 대통령이 임명하고.
대한민국의 주권은 국민에게 있고, 국회는 국민의 보통·평등·직접·비밀선거에 의하여 선출된 국회의원으로 구성한다. 국가는 농업 및 어업을 보호·육성하기 위하여 농·어촌종합개발과 그 지원등 필요한 계획을 수립·시행하여야 한다. 대통령의 임기연장 또는 중임변경을 위한 헌법개정은 그 헌법개정 제안 당시의 대통령에 대하여는 효력이 없다.
국회가 재적의원 과반수의 찬성으로 계엄의 해제를 요구한 때에는 대통령은 이를 해제하여야 한다, 선거에 관한 경비는 법률이 정하는 경우를 제외하고는 정당 또는 후보자에게 부담시킬 수 없다. 그 정치적 중립성은 준수된다. 헌법개정안은 국회가 의결한 후 30일 이내에 국민투표에 붙여 국회의원선거권자 과반수의 투표와 투표자 과반수의 찬성을 얻어야 한다.
내부규율과 사무처리에 관한 규칙을 제정할 수 있다. 대통령에 대한 탄핵소추는 국회재적의원 과반수의 발의와 국회재적의원 3분의 2 이상의 찬성이 있어야 한다. 대통령은 국가의 원수이며. 대통령이 궐위된 때 또는 대통령 당선자가 사망하거나 판결 기타의 사유로 그 자격을 상실한 때에는 60일 이내에 후임자를 선거한다.
-----READING_TIME-----
2

View File

@ -1,3 +1,4 @@
-----CONTENT-----
Lorem 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.
Tation 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
Nec 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.
Vix.
Vix.
-----READING_TIME-----
1