Merge pull request #3984 from wallabag/2.4

Merge 2.4 into master
This commit is contained in:
Jérémy Benoist 2019-05-29 11:14:00 +02:00 committed by GitHub
commit 73ec68b1ff
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
206 changed files with 4709 additions and 1879 deletions

View file

@ -13,5 +13,5 @@ insert_final_newline = true
indent_style = space indent_style = space
indent_size = 2 indent_size = 2
[Makefile] [*akefile]
indent_style = tab indent_style = tab

View file

@ -4,12 +4,6 @@ services:
- rabbitmq - rabbitmq
- redis - redis
# used for HHVM
addons:
apt:
packages:
- tidy
# cache vendor dirs # cache vendor dirs
cache: cache:
apt: true apt: true
@ -21,10 +15,9 @@ cache:
- $HOME/.yarn-cache - $HOME/.yarn-cache
php: php:
- 5.6
- 7.0
- 7.1 - 7.1
- 7.2 - 7.2
- 7.3
- nightly - nightly
node_js: node_js:
@ -38,7 +31,7 @@ env:
matrix: matrix:
fast_finish: true fast_finish: true
include: include:
- php: 7.0 - php: 7.2
env: CS_FIXER=run VALIDATE_TRANSLATION_FILE=run ASSETS=build DB=sqlite env: CS_FIXER=run VALIDATE_TRANSLATION_FILE=run ASSETS=build DB=sqlite
allow_failures: allow_failures:
- php: nightly - php: nightly
@ -58,31 +51,26 @@ install:
before_script: before_script:
- PHP=$TRAVIS_PHP_VERSION - PHP=$TRAVIS_PHP_VERSION
- if [[ ! $PHP = hhvm* ]]; then echo "memory_limit=-1" >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini; fi; - echo "memory_limit=-1" >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini
# xdebug isn't enable for PHP 7.1 - phpenv config-rm xdebug.ini || echo "xdebug not available"
- if [[ ! $PHP = hhvm* ]]; then phpenv config-rm xdebug.ini || echo "xdebug not available"; fi
- composer self-update --no-progress - composer self-update --no-progress
- if [[ $DB = pgsql ]]; then psql -c 'create database wallabag_test;' -U postgres; fi; # install imagick
# increase swap to avoid "proc_open(): fork failed - Cannot allocate memory" - pear config-set preferred_state beta
# this should be removed when no more PHP 5 build will be defined - pecl channel-update pecl.php.net
- sudo swapon -s - yes | pecl install imagick
- sudo fallocate -l 4G /swapfile
- sudo chmod 600 /swapfile
- sudo mkswap /swapfile
- sudo swapon /swapfile
- sudo swapon -s
script: script:
- travis_wait bash composer install -o --no-interaction --no-progress --prefer-dist - travis_wait bash composer install -o --no-interaction --no-progress --prefer-dist
- echo "travis_fold:start:prepare" - echo "travis_fold:start:prepare"
- make prepare DB=$DB - make prepare DB=$DB
- echo "travis_fold:end:prepare" - echo "travis_fold:end:prepare"
- echo "travis_fold:start:fixtures" - make fixtures
- php bin/console doctrine:fixtures:load --no-interaction --env=test
- echo "travis_fold:end:fixtures"
- if [[ $VALIDATE_TRANSLATION_FILE = '' ]]; then ./bin/simple-phpunit -v ; fi; - if [[ $VALIDATE_TRANSLATION_FILE = '' ]]; then SYMFONY_PHPUNIT_VERSION=6.5 ./bin/simple-phpunit -v ; fi;
# PHPStan needs PHPUnit to be installed and cache app to be generated
- if [[ $VALIDATE_TRANSLATION_FILE = '' ]]; then php bin/phpstan analyse src tests --no-progress --level 1 ; fi;
- if [[ $CS_FIXER = run ]]; then php bin/php-cs-fixer fix --verbose --dry-run ; fi; - if [[ $CS_FIXER = run ]]; then php bin/php-cs-fixer fix --verbose --dry-run ; fi;
- if [[ $VALIDATE_TRANSLATION_FILE = run ]]; then php bin/console lint:yaml src/Wallabag/CoreBundle/Resources/translations -v ; fi; - if [[ $VALIDATE_TRANSLATION_FILE = run ]]; then php bin/console lint:yaml src/Wallabag/CoreBundle/Resources/translations -v ; fi;
- if [[ $VALIDATE_TRANSLATION_FILE = run ]]; then php bin/console lint:yaml app/Resources/CraueConfigBundle/translations -v ; fi; - if [[ $VALIDATE_TRANSLATION_FILE = run ]]; then php bin/console lint:yaml app/Resources/CraueConfigBundle/translations -v ; fi;

View file

@ -1,26 +0,0 @@
# see https://zappr.opensource.zalan.do/
autobranch: false
commit: false
approvals:
minimum: 1
ignore: pr_opener
pattern: "^(:\\+1:|👍)$"
veto:
pattern: "^(:\\-1:|👎)$"
from:
orgs:
- wallabag
collaborators: true
specification:
title:
minimum-length:
enabled: true
length: 8
body:
minimum-length:
enabled: true
length: 8
contains-url: false
contains-issue-number: false
template:
differs-from-body: true

View file

@ -1,4 +1,4 @@
Copyright (c) 2013-2017 Nicolas Lœuillet Copyright (c) 2013-current Nicolas Lœuillet
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View file

@ -19,10 +19,10 @@ Then you can install wallabag by executing the following commands:
``` ```
git clone https://github.com/wallabag/wallabag.git git clone https://github.com/wallabag/wallabag.git
cd wallabag && make install cd wallabag && make install
``` ```
Now, [configure a virtual host](https://doc.wallabag.org/en/admin/installation/virtualhosts.html) to use your wallabag. Now, [configure a virtual host](https://doc.wallabag.org/en/admin/installation/virtualhosts.html) to use your wallabag.
# Run on YunoHost # Run on YunoHost
[![Install Wallabag with YunoHost](https://install-app.yunohost.org/install-with-yunohost.png)](https://install-app.yunohost.org/?app=wallabag2) [![Install Wallabag with YunoHost](https://install-app.yunohost.org/install-with-yunohost.png)](https://install-app.yunohost.org/?app=wallabag2)
@ -30,6 +30,6 @@ Now, [configure a virtual host](https://doc.wallabag.org/en/admin/installation/v
Wallabag app for [YunoHost](https://yunohost.org). See [here](https://github.com/YunoHost-Apps/wallabag2_ynh) Wallabag app for [YunoHost](https://yunohost.org). See [here](https://github.com/YunoHost-Apps/wallabag2_ynh)
# License # License
Copyright © 2013-2018 Nicolas Lœuillet <nicolas@loeuillet.org> Copyright © 2013-current Nicolas Lœuillet <nicolas@loeuillet.org>
This work is free. You can redistribute it and/or modify it under the This work is free. You can redistribute it and/or modify it under the
terms of the MIT License. See the COPYING file for more details. terms of the MIT License. See the COPYING file for more details.

View file

@ -1,6 +1,7 @@
<?php <?php
use Symfony\Component\Config\Loader\LoaderInterface; use Symfony\Component\Config\Loader\LoaderInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Kernel; use Symfony\Component\HttpKernel\Kernel;
class AppKernel extends Kernel class AppKernel extends Kernel
@ -32,6 +33,8 @@ class AppKernel extends Kernel
new WhiteOctober\PagerfantaBundle\WhiteOctoberPagerfantaBundle(), new WhiteOctober\PagerfantaBundle\WhiteOctoberPagerfantaBundle(),
new FOS\JsRoutingBundle\FOSJsRoutingBundle(), new FOS\JsRoutingBundle\FOSJsRoutingBundle(),
new BD\GuzzleSiteAuthenticatorBundle\BDGuzzleSiteAuthenticatorBundle(), new BD\GuzzleSiteAuthenticatorBundle\BDGuzzleSiteAuthenticatorBundle(),
new OldSound\RabbitMqBundle\OldSoundRabbitMqBundle(),
new Http\HttplugBundle\HttplugBundle(),
// wallabag bundles // wallabag bundles
new Wallabag\CoreBundle\WallabagCoreBundle(), new Wallabag\CoreBundle\WallabagCoreBundle(),
@ -39,25 +42,32 @@ class AppKernel extends Kernel
new Wallabag\UserBundle\WallabagUserBundle(), new Wallabag\UserBundle\WallabagUserBundle(),
new Wallabag\ImportBundle\WallabagImportBundle(), new Wallabag\ImportBundle\WallabagImportBundle(),
new Wallabag\AnnotationBundle\WallabagAnnotationBundle(), new Wallabag\AnnotationBundle\WallabagAnnotationBundle(),
new OldSound\RabbitMqBundle\OldSoundRabbitMqBundle(),
]; ];
if (in_array($this->getEnvironment(), ['dev', 'test'], true)) { if (in_array($this->getEnvironment(), ['dev', 'test'], true)) {
$bundles[] = new Symfony\Bundle\DebugBundle\DebugBundle(); $bundles[] = new Symfony\Bundle\DebugBundle\DebugBundle();
$bundles[] = new Symfony\Bundle\WebProfilerBundle\WebProfilerBundle(); $bundles[] = new Symfony\Bundle\WebProfilerBundle\WebProfilerBundle();
$bundles[] = new Sensio\Bundle\DistributionBundle\SensioDistributionBundle(); $bundles[] = new Sensio\Bundle\DistributionBundle\SensioDistributionBundle();
$bundles[] = new Sensio\Bundle\GeneratorBundle\SensioGeneratorBundle();
$bundles[] = new Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle(); $bundles[] = new Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle();
$bundles[] = new Symfony\Bundle\WebServerBundle\WebServerBundle();
if ('test' === $this->getEnvironment()) { if ('test' === $this->getEnvironment()) {
$bundles[] = new DAMA\DoctrineTestBundle\DAMADoctrineTestBundle(); $bundles[] = new DAMA\DoctrineTestBundle\DAMADoctrineTestBundle();
} }
if ('dev' === $this->getEnvironment()) {
$bundles[] = new Sensio\Bundle\GeneratorBundle\SensioGeneratorBundle();
$bundles[] = new Symfony\Bundle\WebServerBundle\WebServerBundle();
}
} }
return $bundles; return $bundles;
} }
public function getRootDir()
{
return __DIR__;
}
public function getCacheDir() public function getCacheDir()
{ {
return dirname(__DIR__) . '/var/cache/' . $this->getEnvironment(); return dirname(__DIR__) . '/var/cache/' . $this->getEnvironment();
@ -70,7 +80,8 @@ class AppKernel extends Kernel
public function registerContainerConfiguration(LoaderInterface $loader) public function registerContainerConfiguration(LoaderInterface $loader)
{ {
$loader->load($this->getProjectDir() . '/app/config/config_' . $this->getEnvironment() . '.yml'); $loader->load($this->getRootDir() . '/config/config_' . $this->getEnvironment() . '.yml');
$loader->load(function ($container) { $loader->load(function ($container) {
if ($container->getParameter('use_webpack_dev_server')) { if ($container->getParameter('use_webpack_dev_server')) {
$container->loadFromExtension('framework', [ $container->loadFromExtension('framework', [
@ -86,5 +97,11 @@ class AppKernel extends Kernel
]); ]);
} }
}); });
$loader->load(function (ContainerBuilder $container) {
// $container->setParameter('container.autowiring.strict_mode', true);
// $container->setParameter('container.dumper.inline_class_loader', true);
$container->addObjectResource($this);
});
} }
} }

View file

@ -0,0 +1,51 @@
<?php
namespace Application\Migrations;
use Doctrine\DBAL\Schema\Schema;
use Wallabag\CoreBundle\Doctrine\WallabagMigration;
/**
* Add archived_at column and set its value to updated_at for is_archived entries.
*/
class Version20180405182455 extends WallabagMigration
{
/**
* @param Schema $schema
*/
public function up(Schema $schema)
{
$entryTable = $schema->getTable($this->getTable('entry'));
$this->skipIf($entryTable->hasColumn('archived_at'), 'It seems that you already played this migration.');
$entryTable->addColumn('archived_at', 'datetime', [
'notnull' => false,
]);
}
public function postUp(Schema $schema)
{
$entryTable = $schema->getTable($this->getTable('entry'));
$this->skipIf(!$entryTable->hasColumn('archived_at'), 'Unable to add archived_at colum');
$this->connection->executeQuery(
'UPDATE ' . $this->getTable('entry') . ' SET archived_at = updated_at WHERE is_archived = :is_archived',
[
'is_archived' => true,
]
);
}
/**
* @param Schema $schema
*/
public function down(Schema $schema)
{
$entryTable = $schema->getTable($this->getTable('entry'));
$this->skipIf(!$entryTable->hasColumn('archived_at'), 'It seems that you already played this migration.');
$entryTable->dropColumn('archived_at');
}
}

View file

@ -0,0 +1,45 @@
<?php
namespace Application\Migrations;
use Doctrine\DBAL\Schema\Schema;
use Wallabag\CoreBundle\Doctrine\WallabagMigration;
/**
* Fix varchar field from vendor to work with utf8mb4.
*/
class Version20181128203230 extends WallabagMigration
{
/**
* @param Schema $schema
*/
public function up(Schema $schema)
{
$this->skipIf('mysql' !== $this->connection->getDatabasePlatform()->getName(), 'This migration can only be applied on \'mysql\'.');
$this->addSql('ALTER TABLE ' . $this->getTable('oauth2_access_tokens') . ' CHANGE `token` `token` varchar(191) NOT NULL');
$this->addSql('ALTER TABLE ' . $this->getTable('oauth2_access_tokens') . ' CHANGE `scope` `scope` varchar(191)');
$this->addSql('ALTER TABLE ' . $this->getTable('oauth2_auth_codes') . ' CHANGE `token` `token` varchar(191) NOT NULL');
$this->addSql('ALTER TABLE ' . $this->getTable('oauth2_auth_codes') . ' CHANGE `scope` `scope` varchar(191)');
$this->addSql('ALTER TABLE ' . $this->getTable('oauth2_refresh_tokens') . ' CHANGE `token` `token` varchar(191) NOT NULL');
$this->addSql('ALTER TABLE ' . $this->getTable('oauth2_refresh_tokens') . ' CHANGE `scope` `scope` varchar(191)');
$this->addSql('ALTER TABLE ' . $this->getTable('craue_config_setting') . ' CHANGE `name` `name` varchar(191)');
$this->addSql('ALTER TABLE ' . $this->getTable('craue_config_setting') . ' CHANGE `section` `section` varchar(191)');
$this->addSql('ALTER TABLE ' . $this->getTable('craue_config_setting') . ' CHANGE `value` `value` varchar(191)');
}
public function down(Schema $schema)
{
$this->skipIf('mysql' !== $this->connection->getDatabasePlatform()->getName(), 'This migration can only be applied on \'mysql\'.');
$this->addSql('ALTER TABLE ' . $this->getTable('oauth2_access_tokens') . ' CHANGE `token` `token` varchar(255) NOT NULL');
$this->addSql('ALTER TABLE ' . $this->getTable('oauth2_access_tokens') . ' CHANGE `scope` `scope` varchar(255)');
$this->addSql('ALTER TABLE ' . $this->getTable('oauth2_auth_codes') . ' CHANGE `token` `token` varchar(255) NOT NULL');
$this->addSql('ALTER TABLE ' . $this->getTable('oauth2_auth_codes') . ' CHANGE `scope` `scope` varchar(255)');
$this->addSql('ALTER TABLE ' . $this->getTable('oauth2_refresh_tokens') . ' CHANGE `token` `token` varchar(255) NOT NULL');
$this->addSql('ALTER TABLE ' . $this->getTable('oauth2_refresh_tokens') . ' CHANGE `scope` `scope` varchar(255)');
$this->addSql('ALTER TABLE ' . $this->getTable('craue_config_setting') . ' CHANGE `name` `name` varchar(255)');
$this->addSql('ALTER TABLE ' . $this->getTable('craue_config_setting') . ' CHANGE `section` `section` varchar(255)');
$this->addSql('ALTER TABLE ' . $this->getTable('craue_config_setting') . ' CHANGE `value` `value` varchar(255)');
}
}

View file

@ -0,0 +1,76 @@
<?php
namespace Application\Migrations;
use Doctrine\DBAL\Schema\Schema;
use Wallabag\CoreBundle\Doctrine\WallabagMigration;
/**
* Add 2fa OTP stuff.
*/
final class Version20181202073750 extends WallabagMigration
{
public function up(Schema $schema): void
{
switch ($this->connection->getDatabasePlatform()->getName()) {
case 'sqlite':
$this->addSql('DROP INDEX UNIQ_1D63E7E5C05FB297');
$this->addSql('DROP INDEX UNIQ_1D63E7E5A0D96FBF');
$this->addSql('DROP INDEX UNIQ_1D63E7E592FC23A8');
$this->addSql('CREATE TEMPORARY TABLE __temp__' . $this->getTable('user', true) . ' AS SELECT id, username, username_canonical, email, email_canonical, enabled, salt, password, last_login, confirmation_token, password_requested_at, roles, name, created_at, updated_at, authCode, twoFactorAuthentication FROM ' . $this->getTable('user', true) . '');
$this->addSql('DROP TABLE ' . $this->getTable('user', true) . '');
$this->addSql('CREATE TABLE ' . $this->getTable('user', true) . ' (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, username VARCHAR(180) NOT NULL COLLATE BINARY, username_canonical VARCHAR(180) NOT NULL COLLATE BINARY, email VARCHAR(180) NOT NULL COLLATE BINARY, email_canonical VARCHAR(180) NOT NULL COLLATE BINARY, enabled BOOLEAN NOT NULL, password VARCHAR(255) NOT NULL COLLATE BINARY, last_login DATETIME DEFAULT NULL, password_requested_at DATETIME DEFAULT NULL, name CLOB DEFAULT NULL COLLATE BINARY, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, authCode INTEGER DEFAULT NULL, emailTwoFactor BOOLEAN NOT NULL, salt VARCHAR(255) DEFAULT NULL, confirmation_token VARCHAR(180) DEFAULT NULL, roles CLOB NOT NULL --(DC2Type:array)
, googleAuthenticatorSecret VARCHAR(255) DEFAULT NULL, backupCodes CLOB DEFAULT NULL --(DC2Type:json_array)
)');
$this->addSql('INSERT INTO ' . $this->getTable('user', true) . ' (id, username, username_canonical, email, email_canonical, enabled, salt, password, last_login, confirmation_token, password_requested_at, roles, name, created_at, updated_at, authCode, emailTwoFactor) SELECT id, username, username_canonical, email, email_canonical, enabled, salt, password, last_login, confirmation_token, password_requested_at, roles, name, created_at, updated_at, authCode, twoFactorAuthentication FROM __temp__' . $this->getTable('user', true) . '');
$this->addSql('DROP TABLE __temp__' . $this->getTable('user', true) . '');
$this->addSql('CREATE UNIQUE INDEX UNIQ_1D63E7E5C05FB297 ON ' . $this->getTable('user', true) . ' (confirmation_token)');
$this->addSql('CREATE UNIQUE INDEX UNIQ_1D63E7E5A0D96FBF ON ' . $this->getTable('user', true) . ' (email_canonical)');
$this->addSql('CREATE UNIQUE INDEX UNIQ_1D63E7E592FC23A8 ON ' . $this->getTable('user', true) . ' (username_canonical)');
break;
case 'mysql':
$this->addSql('ALTER TABLE ' . $this->getTable('user') . ' ADD googleAuthenticatorSecret VARCHAR(191) DEFAULT NULL');
$this->addSql('ALTER TABLE ' . $this->getTable('user') . ' CHANGE twoFactorAuthentication emailTwoFactor BOOLEAN NOT NULL');
$this->addSql('ALTER TABLE ' . $this->getTable('user') . ' DROP trusted');
$this->addSql('ALTER TABLE ' . $this->getTable('user') . ' ADD backupCodes LONGTEXT DEFAULT NULL COMMENT \'(DC2Type:json_array)\'');
break;
case 'postgresql':
$this->addSql('ALTER TABLE ' . $this->getTable('user') . ' ADD googleAuthenticatorSecret VARCHAR(191) DEFAULT NULL');
$this->addSql('ALTER TABLE ' . $this->getTable('user') . ' RENAME COLUMN twofactorauthentication TO emailTwoFactor');
$this->addSql('ALTER TABLE ' . $this->getTable('user') . ' DROP trusted');
$this->addSql('ALTER TABLE ' . $this->getTable('user') . ' ADD backupCodes TEXT DEFAULT NULL');
break;
}
}
public function down(Schema $schema): void
{
switch ($this->connection->getDatabasePlatform()->getName()) {
case 'sqlite':
$this->addSql('DROP INDEX UNIQ_1D63E7E592FC23A8');
$this->addSql('DROP INDEX UNIQ_1D63E7E5A0D96FBF');
$this->addSql('DROP INDEX UNIQ_1D63E7E5C05FB297');
$this->addSql('CREATE TEMPORARY TABLE __temp__' . $this->getTable('user', true) . ' AS SELECT id, username, username_canonical, email, email_canonical, enabled, salt, password, last_login, confirmation_token, password_requested_at, roles, name, created_at, updated_at, authCode, emailTwoFactor FROM "' . $this->getTable('user', true) . '"');
$this->addSql('DROP TABLE "' . $this->getTable('user', true) . '"');
$this->addSql('CREATE TABLE "' . $this->getTable('user', true) . '" (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, username VARCHAR(180) NOT NULL, username_canonical VARCHAR(180) NOT NULL, email VARCHAR(180) NOT NULL, email_canonical VARCHAR(180) NOT NULL, enabled BOOLEAN NOT NULL, password VARCHAR(255) NOT NULL, last_login DATETIME DEFAULT NULL, password_requested_at DATETIME DEFAULT NULL, name CLOB DEFAULT NULL, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, authCode INTEGER DEFAULT NULL, twoFactorAuthentication BOOLEAN NOT NULL, salt VARCHAR(255) NOT NULL COLLATE BINARY, confirmation_token VARCHAR(255) DEFAULT NULL COLLATE BINARY, roles CLOB NOT NULL COLLATE BINARY, trusted CLOB DEFAULT NULL COLLATE BINARY)');
$this->addSql('INSERT INTO "' . $this->getTable('user', true) . '" (id, username, username_canonical, email, email_canonical, enabled, salt, password, last_login, confirmation_token, password_requested_at, roles, name, created_at, updated_at, authCode, twoFactorAuthentication) SELECT id, username, username_canonical, email, email_canonical, enabled, salt, password, last_login, confirmation_token, password_requested_at, roles, name, created_at, updated_at, authCode, emailTwoFactor FROM __temp__' . $this->getTable('user', true) . '');
$this->addSql('DROP TABLE __temp__' . $this->getTable('user', true) . '');
$this->addSql('CREATE UNIQUE INDEX UNIQ_1D63E7E592FC23A8 ON "' . $this->getTable('user', true) . '" (username_canonical)');
$this->addSql('CREATE UNIQUE INDEX UNIQ_1D63E7E5A0D96FBF ON "' . $this->getTable('user', true) . '" (email_canonical)');
$this->addSql('CREATE UNIQUE INDEX UNIQ_1D63E7E5C05FB297 ON "' . $this->getTable('user', true) . '" (confirmation_token)');
break;
case 'mysql':
$this->addSql('ALTER TABLE `' . $this->getTable('user') . '` DROP googleAuthenticatorSecret');
$this->addSql('ALTER TABLE `' . $this->getTable('user') . '` CHANGE emailtwofactor twoFactorAuthentication BOOLEAN NOT NULL');
$this->addSql('ALTER TABLE `' . $this->getTable('user') . '` ADD trusted TEXT DEFAULT NULL');
$this->addSql('ALTER TABLE `' . $this->getTable('user') . '` DROP backupCodes');
break;
case 'postgresql':
$this->addSql('ALTER TABLE ' . $this->getTable('user') . ' DROP googleAuthenticatorSecret');
$this->addSql('ALTER TABLE ' . $this->getTable('user') . ' RENAME COLUMN emailTwoFactor TO twofactorauthentication');
$this->addSql('ALTER TABLE ' . $this->getTable('user') . ' ADD trusted TEXT DEFAULT NULL');
$this->addSql('ALTER TABLE ' . $this->getTable('user') . ' DROP backupCodes');
break;
}
}
}

View file

@ -0,0 +1,32 @@
<?php
namespace Application\Migrations;
use Doctrine\DBAL\Schema\Schema;
use Wallabag\CoreBundle\Doctrine\WallabagMigration;
/**
* Add updated_at fields to site_credential table.
*/
final class Version20190117131816 extends WallabagMigration
{
public function up(Schema $schema): void
{
$siteCredentialTable = $schema->getTable($this->getTable('site_credential'));
$this->skipIf($siteCredentialTable->hasColumn('updated_at'), 'It seems that you already played this migration.');
$siteCredentialTable->addColumn('updated_at', 'datetime', [
'notnull' => false,
]);
}
public function down(Schema $schema): void
{
$siteCredentialTable = $schema->getTable($this->getTable('site_credential'));
$this->skipIf(!$siteCredentialTable->hasColumn('updated_at'), 'It seems that you already played this migration.');
$siteCredentialTable->dropColumn('updated_at');
}
}

View file

@ -0,0 +1,147 @@
<?php
namespace Application\Migrations;
use Doctrine\DBAL\Schema\Schema;
use Wallabag\CoreBundle\Doctrine\WallabagMigration;
/**
* Add missing entries in craue_config_setting.
*/
final class Version20190129120000 extends WallabagMigration
{
private $settings = [
[
'name' => 'carrot',
'value' => '1',
'section' => 'entry',
],
[
'name' => 'share_diaspora',
'value' => '1',
'section' => 'entry',
],
[
'name' => 'diaspora_url',
'value' => 'http://diasporapod.com',
'section' => 'entry',
],
[
'name' => 'share_shaarli',
'value' => '1',
'section' => 'entry',
],
[
'name' => 'shaarli_url',
'value' => 'http://myshaarli.com',
'section' => 'entry',
],
[
'name' => 'share_mail',
'value' => '1',
'section' => 'entry',
],
[
'name' => 'share_twitter',
'value' => '1',
'section' => 'entry',
],
[
'name' => 'show_printlink',
'value' => '1',
'section' => 'entry',
],
[
'name' => 'export_epub',
'value' => '1',
'section' => 'export',
],
[
'name' => 'export_mobi',
'value' => '1',
'section' => 'export',
],
[
'name' => 'export_pdf',
'value' => '1',
'section' => 'export',
],
[
'name' => 'export_csv',
'value' => '1',
'section' => 'export',
],
[
'name' => 'export_json',
'value' => '1',
'section' => 'export',
],
[
'name' => 'export_txt',
'value' => '1',
'section' => 'export',
],
[
'name' => 'export_xml',
'value' => '1',
'section' => 'export',
],
[
'name' => 'piwik_enabled',
'value' => '0',
'section' => 'analytics',
],
[
'name' => 'piwik_host',
'value' => 'v2.wallabag.org',
'section' => 'analytics',
],
[
'name' => 'piwik_site_id',
'value' => '1',
'section' => 'analytics',
],
[
'name' => 'demo_mode_enabled',
'value' => '0',
'section' => 'misc',
],
[
'name' => 'demo_mode_username',
'value' => 'wallabag',
'section' => 'misc',
],
[
'name' => 'wallabag_support_url',
'value' => 'https://www.wallabag.org/pages/support.html',
'section' => 'misc',
],
];
/**
* @param Schema $schema
*/
public function up(Schema $schema)
{
foreach ($this->settings as $setting) {
$settingEnabled = $this->container
->get('doctrine.orm.default_entity_manager')
->getConnection()
->fetchArray('SELECT * FROM ' . $this->getTable('craue_config_setting') . " WHERE name = '" . $setting['name'] . "'");
if (false !== $settingEnabled) {
continue;
}
$this->addSql('INSERT INTO ' . $this->getTable('craue_config_setting') . " (name, value, section) VALUES ('" . $setting['name'] . "', '" . $setting['value'] . "', '" . $setting['section'] . "');");
}
}
/**
* @param Schema $schema
*/
public function down(Schema $schema)
{
$this->skipIf(true, 'These settings are required and should not be removed.');
}
}

View file

@ -0,0 +1,42 @@
<?php
namespace Application\Migrations;
use Doctrine\DBAL\Schema\Schema;
use Wallabag\CoreBundle\Doctrine\WallabagMigration;
/**
* Add hashed_url in entry.
*/
class Version20190401105353 extends WallabagMigration
{
/**
* @param Schema $schema
*/
public function up(Schema $schema)
{
$entryTable = $schema->getTable($this->getTable('entry'));
$this->skipIf($entryTable->hasColumn('hashed_url'), 'It seems that you already played this migration.');
$entryTable->addColumn('hashed_url', 'text', [
'length' => 40,
'notnull' => false,
]);
$entryTable->addIndex(['user_id', 'hashed_url'], 'hashed_url_user_id', [], ['lengths' => [null, 40]]);
}
/**
* @param Schema $schema
*/
public function down(Schema $schema)
{
$entryTable = $schema->getTable($this->getTable('entry'));
$this->skipIf(!$entryTable->hasColumn('hashed_url'), 'It seems that you already played this migration.');
$entryTable->dropIndex('hashed_url_user_id');
$entryTable->dropColumn('hashed_url');
}
}

View file

@ -0,0 +1,58 @@
<?php
namespace Application\Migrations;
use Doctrine\DBAL\Schema\Schema;
use Wallabag\CoreBundle\Doctrine\WallabagMigration;
/**
* Rename rss_token & rss_limit to feed_token & feed_limit.
*/
final class Version20190425115043 extends WallabagMigration
{
public function up(Schema $schema): void
{
switch ($this->connection->getDatabasePlatform()->getName()) {
case 'sqlite':
$this->addSql('DROP INDEX UNIQ_87E64C53A76ED395');
$this->addSql('CREATE TEMPORARY TABLE __temp__' . $this->getTable('config', true) . ' AS SELECT id, user_id, theme, items_per_page, language, rss_token, rss_limit, reading_speed, pocket_consumer_key, action_mark_as_read, list_mode FROM ' . $this->getTable('config', true));
$this->addSql('DROP TABLE ' . $this->getTable('config', true));
$this->addSql('CREATE TABLE ' . $this->getTable('config', true) . ' (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, user_id INTEGER DEFAULT NULL, theme VARCHAR(255) NOT NULL COLLATE BINARY, items_per_page INTEGER NOT NULL, language VARCHAR(255) NOT NULL COLLATE BINARY, reading_speed DOUBLE PRECISION DEFAULT NULL, pocket_consumer_key VARCHAR(255) DEFAULT NULL COLLATE BINARY, action_mark_as_read INTEGER DEFAULT 0, list_mode INTEGER DEFAULT NULL, feed_token VARCHAR(255) DEFAULT NULL, feed_limit INTEGER DEFAULT NULL, CONSTRAINT FK_87E64C53A76ED395 FOREIGN KEY (user_id) REFERENCES "wallabag_user" (id) NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO ' . $this->getTable('config', true) . ' (id, user_id, theme, items_per_page, language, feed_token, feed_limit, reading_speed, pocket_consumer_key, action_mark_as_read, list_mode) SELECT id, user_id, theme, items_per_page, language, rss_token, rss_limit, reading_speed, pocket_consumer_key, action_mark_as_read, list_mode FROM __temp__' . $this->getTable('config', true));
$this->addSql('DROP TABLE __temp__' . $this->getTable('config', true));
$this->addSql('CREATE UNIQUE INDEX UNIQ_87E64C53A76ED395 ON ' . $this->getTable('config', true) . ' (user_id)');
break;
case 'mysql':
$this->addSql('ALTER TABLE ' . $this->getTable('config') . ' CHANGE rss_token feed_token VARCHAR(255) DEFAULT NULL');
$this->addSql('ALTER TABLE ' . $this->getTable('config') . ' CHANGE rss_limit feed_limit INT DEFAULT NULL');
break;
case 'postgresql':
$this->addSql('ALTER TABLE ' . $this->getTable('config') . ' RENAME COLUMN rss_token TO feed_token');
$this->addSql('ALTER TABLE ' . $this->getTable('config') . ' RENAME COLUMN rss_limit TO feed_limit');
break;
}
}
public function down(Schema $schema): void
{
switch ($this->connection->getDatabasePlatform()->getName()) {
case 'sqlite':
$this->addSql('DROP INDEX UNIQ_87E64C53A76ED395');
$this->addSql('CREATE TEMPORARY TABLE __temp__' . $this->getTable('config', true) . ' AS SELECT id, user_id, theme, items_per_page, language, feed_token, feed_limit, reading_speed, pocket_consumer_key, action_mark_as_read, list_mode FROM "' . $this->getTable('config', true) . '"');
$this->addSql('DROP TABLE "' . $this->getTable('config', true) . '"');
$this->addSql('CREATE TABLE "' . $this->getTable('config', true) . '" (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, user_id INTEGER DEFAULT NULL, theme VARCHAR(255) NOT NULL, items_per_page INTEGER NOT NULL, language VARCHAR(255) NOT NULL, reading_speed DOUBLE PRECISION DEFAULT NULL, pocket_consumer_key VARCHAR(255) DEFAULT NULL, action_mark_as_read INTEGER DEFAULT 0, list_mode INTEGER DEFAULT NULL, rss_token VARCHAR(255) DEFAULT NULL COLLATE BINARY, rss_limit INTEGER DEFAULT NULL)');
$this->addSql('INSERT INTO "' . $this->getTable('config', true) . '" (id, user_id, theme, items_per_page, language, rss_token, rss_limit, reading_speed, pocket_consumer_key, action_mark_as_read, list_mode) SELECT id, user_id, theme, items_per_page, language, feed_token, feed_limit, reading_speed, pocket_consumer_key, action_mark_as_read, list_mode FROM __temp__' . $this->getTable('config', true));
$this->addSql('DROP TABLE __temp__' . $this->getTable('config', true));
$this->addSql('CREATE UNIQUE INDEX UNIQ_87E64C53A76ED395 ON "' . $this->getTable('config', true) . '" (user_id)');
break;
case 'mysql':
$this->addSql('ALTER TABLE ' . $this->getTable('config') . ' CHANGE feed_token rss_token');
$this->addSql('ALTER TABLE ' . $this->getTable('config') . ' CHANGE feed_limit rss_limit');
break;
case 'postgresql':
$this->addSql('ALTER TABLE ' . $this->getTable('config') . ' RENAME COLUMN feed_token TO rss_token');
$this->addSql('ALTER TABLE ' . $this->getTable('config') . ' RENAME COLUMN feed_limit TO rss_limit');
break;
}
}
}

View file

@ -0,0 +1,96 @@
<?php
namespace Application\Migrations;
use Doctrine\DBAL\Migrations\SkipMigrationException;
use Doctrine\DBAL\Schema\Schema;
use Wallabag\CoreBundle\Doctrine\WallabagMigration;
/**
* Enable cascade delete when deleting a user on:
* - oauth2_access_tokens
* - oauth2_clients
* - oauth2_refresh_tokens
* - oauth2_auth_codes.
*/
final class Version20190510141130 extends WallabagMigration
{
public function up(Schema $schema): void
{
switch ($this->connection->getDatabasePlatform()->getName()) {
case 'sqlite':
$this->addSql('DROP INDEX IDX_368A4209A76ED395');
$this->addSql('DROP INDEX IDX_368A420919EB6921');
$this->addSql('DROP INDEX UNIQ_368A42095F37A13B');
$this->addSql('CREATE TEMPORARY TABLE __temp__' . $this->getTable('oauth2_access_tokens', true) . ' AS SELECT id, client_id, user_id, token, expires_at, scope FROM ' . $this->getTable('oauth2_access_tokens', true));
$this->addSql('DROP TABLE ' . $this->getTable('oauth2_access_tokens', true));
$this->addSql('CREATE TABLE ' . $this->getTable('oauth2_access_tokens', true) . ' (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, client_id INTEGER NOT NULL, user_id INTEGER DEFAULT NULL, expires_at INTEGER DEFAULT NULL, token VARCHAR(191) NOT NULL, scope VARCHAR(191) NULL, CONSTRAINT FK_368A420919EB6921 FOREIGN KEY (client_id) REFERENCES ' . $this->getTable('oauth2_clients', true) . ' (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_368A4209A76ED395 FOREIGN KEY (user_id) REFERENCES "wallabag_user" (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO ' . $this->getTable('oauth2_access_tokens', true) . ' (id, client_id, user_id, token, expires_at, scope) SELECT id, client_id, user_id, token, expires_at, scope FROM __temp__' . $this->getTable('oauth2_access_tokens', true));
$this->addSql('DROP TABLE __temp__' . $this->getTable('oauth2_access_tokens', true));
$this->addSql('CREATE INDEX IDX_368A4209A76ED395 ON ' . $this->getTable('oauth2_access_tokens', true) . ' (user_id)');
$this->addSql('CREATE INDEX IDX_368A420919EB6921 ON ' . $this->getTable('oauth2_access_tokens', true) . ' (client_id)');
$this->addSql('DROP INDEX IDX_635D765EA76ED395');
$this->addSql('CREATE TEMPORARY TABLE __temp__' . $this->getTable('oauth2_clients', true) . ' AS SELECT id, user_id, random_id, secret, redirect_uris, allowed_grant_types, name FROM ' . $this->getTable('oauth2_clients', true));
$this->addSql('DROP TABLE ' . $this->getTable('oauth2_clients', true));
$this->addSql('CREATE TABLE ' . $this->getTable('oauth2_clients', true) . ' (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, user_id INTEGER DEFAULT NULL, random_id VARCHAR(255) NOT NULL COLLATE BINARY, secret VARCHAR(255) NOT NULL COLLATE BINARY, name CLOB NOT NULL COLLATE BINARY, redirect_uris CLOB NOT NULL, allowed_grant_types CLOB NOT NULL, CONSTRAINT FK_635D765EA76ED395 FOREIGN KEY (user_id) REFERENCES "wallabag_user" (id) NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO ' . $this->getTable('oauth2_clients', true) . ' (id, user_id, random_id, secret, redirect_uris, allowed_grant_types, name) SELECT id, user_id, random_id, secret, redirect_uris, allowed_grant_types, name FROM __temp__' . $this->getTable('oauth2_clients', true));
$this->addSql('DROP TABLE __temp__' . $this->getTable('oauth2_clients', true));
$this->addSql('CREATE INDEX IDX_635D765EA76ED395 ON ' . $this->getTable('oauth2_clients', true) . ' (user_id)');
$this->addSql('DROP INDEX IDX_20C9FB24A76ED395');
$this->addSql('DROP INDEX IDX_20C9FB2419EB6921');
$this->addSql('DROP INDEX UNIQ_20C9FB245F37A13B');
$this->addSql('CREATE TEMPORARY TABLE __temp__' . $this->getTable('oauth2_refresh_tokens', true) . ' AS SELECT id, client_id, user_id, token, expires_at, scope FROM ' . $this->getTable('oauth2_refresh_tokens', true));
$this->addSql('DROP TABLE ' . $this->getTable('oauth2_refresh_tokens', true));
$this->addSql('CREATE TABLE ' . $this->getTable('oauth2_refresh_tokens', true) . ' (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, client_id INTEGER NOT NULL, user_id INTEGER DEFAULT NULL, expires_at INTEGER DEFAULT NULL, token VARCHAR(191) NOT NULL, scope VARCHAR(191) NULL, CONSTRAINT FK_20C9FB2419EB6921 FOREIGN KEY (client_id) REFERENCES ' . $this->getTable('oauth2_clients', true) . ' (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_20C9FB24A76ED395 FOREIGN KEY (user_id) REFERENCES "wallabag_user" (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO ' . $this->getTable('oauth2_refresh_tokens', true) . ' (id, client_id, user_id, token, expires_at, scope) SELECT id, client_id, user_id, token, expires_at, scope FROM __temp__' . $this->getTable('oauth2_refresh_tokens', true));
$this->addSql('DROP TABLE __temp__' . $this->getTable('oauth2_refresh_tokens', true));
$this->addSql('CREATE INDEX IDX_20C9FB24A76ED395 ON ' . $this->getTable('oauth2_refresh_tokens', true) . ' (user_id)');
$this->addSql('CREATE INDEX IDX_20C9FB2419EB6921 ON ' . $this->getTable('oauth2_refresh_tokens', true) . ' (client_id)');
$this->addSql('DROP INDEX IDX_EE52E3FAA76ED395');
$this->addSql('DROP INDEX IDX_EE52E3FA19EB6921');
$this->addSql('DROP INDEX UNIQ_EE52E3FA5F37A13B');
$this->addSql('CREATE TEMPORARY TABLE __temp__' . $this->getTable('oauth2_auth_codes', true) . ' AS SELECT id, client_id, user_id, token, redirect_uri, expires_at, scope FROM ' . $this->getTable('oauth2_auth_codes', true));
$this->addSql('DROP TABLE ' . $this->getTable('oauth2_auth_codes', true));
$this->addSql('CREATE TABLE ' . $this->getTable('oauth2_auth_codes', true) . ' (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, client_id INTEGER NOT NULL, user_id INTEGER DEFAULT NULL, redirect_uri CLOB NOT NULL COLLATE BINARY, expires_at INTEGER DEFAULT NULL, token VARCHAR(191) NOT NULL, scope VARCHAR(191) NULL, CONSTRAINT FK_EE52E3FA19EB6921 FOREIGN KEY (client_id) REFERENCES ' . $this->getTable('oauth2_clients', true) . ' (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_EE52E3FAA76ED395 FOREIGN KEY (user_id) REFERENCES "wallabag_user" (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO ' . $this->getTable('oauth2_auth_codes', true) . ' (id, client_id, user_id, token, redirect_uri, expires_at, scope) SELECT id, client_id, user_id, token, redirect_uri, expires_at, scope FROM __temp__' . $this->getTable('oauth2_auth_codes', true));
$this->addSql('DROP TABLE __temp__' . $this->getTable('oauth2_auth_codes', true));
$this->addSql('CREATE INDEX IDX_EE52E3FAA76ED395 ON ' . $this->getTable('oauth2_auth_codes', true) . ' (user_id)');
$this->addSql('CREATE INDEX IDX_EE52E3FA19EB6921 ON ' . $this->getTable('oauth2_auth_codes', true) . ' (client_id)');
break;
case 'mysql':
$this->addSql('ALTER TABLE ' . $this->getTable('oauth2_access_tokens') . ' DROP FOREIGN KEY FK_368A4209A76ED395');
$this->addSql('ALTER TABLE ' . $this->getTable('oauth2_access_tokens') . ' ADD CONSTRAINT FK_368A4209A76ED395 FOREIGN KEY (user_id) REFERENCES `wallabag_user` (id) ON DELETE CASCADE');
$this->addSql('ALTER TABLE ' . $this->getTable('oauth2_clients') . ' DROP FOREIGN KEY IDX_user_oauth_client');
$this->addSql('ALTER TABLE ' . $this->getTable('oauth2_clients') . ' ADD CONSTRAINT FK_635D765EA76ED395 FOREIGN KEY (user_id) REFERENCES `wallabag_user` (id)');
$this->addSql('ALTER TABLE ' . $this->getTable('oauth2_refresh_tokens') . ' DROP FOREIGN KEY FK_20C9FB24A76ED395');
$this->addSql('ALTER TABLE ' . $this->getTable('oauth2_refresh_tokens') . ' ADD CONSTRAINT FK_20C9FB24A76ED395 FOREIGN KEY (user_id) REFERENCES `wallabag_user` (id) ON DELETE CASCADE');
$this->addSql('ALTER TABLE ' . $this->getTable('oauth2_auth_codes') . ' DROP FOREIGN KEY FK_EE52E3FAA76ED395');
$this->addSql('ALTER TABLE ' . $this->getTable('oauth2_auth_codes') . ' ADD CONSTRAINT FK_EE52E3FAA76ED395 FOREIGN KEY (user_id) REFERENCES `wallabag_user` (id) ON DELETE CASCADE');
break;
case 'postgresql':
$this->addSql('ALTER TABLE ' . $this->getTable('oauth2_access_tokens') . ' DROP CONSTRAINT FK_368A4209A76ED395');
$this->addSql('ALTER TABLE ' . $this->getTable('oauth2_access_tokens') . ' ADD CONSTRAINT FK_368A4209A76ED395 FOREIGN KEY (user_id) REFERENCES "wallabag_user" (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('ALTER TABLE ' . $this->getTable('oauth2_clients') . ' DROP CONSTRAINT idx_user_oauth_client');
$this->addSql('ALTER TABLE ' . $this->getTable('oauth2_clients') . ' ADD CONSTRAINT FK_635D765EA76ED395 FOREIGN KEY (user_id) REFERENCES "wallabag_user" (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('ALTER TABLE ' . $this->getTable('oauth2_refresh_tokens') . ' DROP CONSTRAINT FK_20C9FB24A76ED395');
$this->addSql('ALTER TABLE ' . $this->getTable('oauth2_refresh_tokens') . ' ADD CONSTRAINT FK_20C9FB24A76ED395 FOREIGN KEY (user_id) REFERENCES "wallabag_user" (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('ALTER TABLE ' . $this->getTable('oauth2_auth_codes') . ' DROP CONSTRAINT FK_EE52E3FAA76ED395');
$this->addSql('ALTER TABLE ' . $this->getTable('oauth2_auth_codes') . ' ADD CONSTRAINT FK_EE52E3FAA76ED395 FOREIGN KEY (user_id) REFERENCES "wallabag_user" (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
break;
}
}
public function down(Schema $schema): void
{
throw new SkipMigrationException('Too complex ...');
}
}

View file

@ -70,4 +70,41 @@ $(document).ready(() => {
retrievePercent(x.entryId, true); retrievePercent(x.entryId, true);
}); });
} }
document.querySelectorAll('[data-handler=tag-rename]').forEach((item) => {
const current = item;
current.wallabag_edit_mode = false;
current.onclick = (event) => {
const target = event.currentTarget;
if (target.wallabag_edit_mode === false) {
$(target.parentNode.querySelector('[data-handle=tag-link]')).addClass('hidden');
$(target.parentNode.querySelector('[data-handle=tag-rename-form]')).removeClass('hidden');
target.parentNode.querySelector('[data-handle=tag-rename-form] input').focus();
target.querySelector('.material-icons').innerHTML = 'done';
target.wallabag_edit_mode = true;
} else {
target.parentNode.querySelector('[data-handle=tag-rename-form]').submit();
}
};
});
// mimic radio button because emailTwoFactor is a boolean
$('#update_user_googleTwoFactor').on('change', () => {
$('#update_user_emailTwoFactor').prop('checked', false);
});
$('#update_user_emailTwoFactor').on('change', () => {
$('#update_user_googleTwoFactor').prop('checked', false);
});
// same mimic for super admin
$('#user_googleTwoFactor').on('change', () => {
$('#user_emailTwoFactor').prop('checked', false);
});
$('#user_emailTwoFactor').on('change', () => {
$('#user_googleTwoFactor').prop('checked', false);
});
}); });

View file

@ -85,7 +85,7 @@ blockquote {
color: #999; color: #999;
} }
.icon-rss { .icon-feed {
background-color: #000; background-color: #000;
color: #fff; color: #fff;
padding: 0.2em 0.5em; padding: 0.2em 0.5em;
@ -101,8 +101,8 @@ blockquote {
margin-bottom: 0.5em; margin-bottom: 0.5em;
} }
.icon-rss:hover, .icon-feed:hover,
.icon-rss:focus { .icon-feed:focus {
background-color: #fff; background-color: #fff;
color: #000; color: #000;
text-decoration: none; text-decoration: none;

View file

@ -295,6 +295,15 @@ div.pagination ul {
} }
} }
.hide { .card-tag-form {
display: inline-block;
}
.card-tag-form input[type="text"] {
min-width: 20em;
}
.hide,
.hidden {
display: none; display: none;
} }

View file

@ -136,7 +136,7 @@
content: "\ea3a"; content: "\ea3a";
} }
.icon-rss::before { .icon-feed::before {
content: "\e808"; content: "\e808";
} }

View file

@ -197,6 +197,17 @@ a.original:not(.waves-effect) {
flex-grow: 1; flex-grow: 1;
} }
.card-tag-form {
display: flex;
min-width: 100px;
flex-grow: 1;
}
.card-tag-form input {
margin-bottom: 0;
height: 2rem;
}
.card-tag-rss { .card-tag-rss {
display: flex; display: flex;
} }

View file

@ -8,7 +8,7 @@ import 'materialize-css/dist/js/materialize';
import '../_global/index'; import '../_global/index';
/* Tools */ /* Tools */
import { initExport, initFilters } from './js/tools'; import { initExport, initFilters, initRandom } from './js/tools';
/* Import shortcuts */ /* Import shortcuts */
import './js/shortcuts/main'; import './js/shortcuts/main';
@ -32,8 +32,10 @@ $(document).ready(() => {
format: 'dd/mm/yyyy', format: 'dd/mm/yyyy',
container: 'body', container: 'body',
}); });
initFilters(); initFilters();
initExport(); initExport();
initRandom();
const toggleNav = (toShow, toFocus) => { const toggleNav = (toShow, toFocus) => {
$('.nav-panel-actions').hide(100); $('.nav-panel-actions').hide(100);
@ -48,25 +50,30 @@ $(document).ready(() => {
$('#tag_label').focus(); $('#tag_label').focus();
return false; return false;
}); });
$('#nav-btn-add').on('click', () => { $('#nav-btn-add').on('click', () => {
toggleNav('.nav-panel-add', '#entry_url'); toggleNav('.nav-panel-add', '#entry_url');
return false; return false;
}); });
const materialAddForm = $('.nav-panel-add'); const materialAddForm = $('.nav-panel-add');
materialAddForm.on('submit', () => { materialAddForm.on('submit', () => {
materialAddForm.addClass('disabled'); materialAddForm.addClass('disabled');
$('input#entry_url', materialAddForm).prop('readonly', true).trigger('blur'); $('input#entry_url', materialAddForm).prop('readonly', true).trigger('blur');
}); });
$('#nav-btn-search').on('click', () => { $('#nav-btn-search').on('click', () => {
toggleNav('.nav-panel-search', '#search_entry_term'); toggleNav('.nav-panel-search', '#search_entry_term');
return false; return false;
}); });
$('.close').on('click', (e) => { $('.close').on('click', (e) => {
$(e.target).parent('.nav-panel-item').hide(100); $(e.target).parent('.nav-panel-item').hide(100);
$('.nav-panel-actions').show(100); $('.nav-panel-actions').show(100);
$('.nav-panels').css('background', 'transparent'); $('.nav-panels').css('background', 'transparent');
return false; return false;
}); });
$(window).scroll(() => { $(window).scroll(() => {
const s = $(window).scrollTop(); const s = $(window).scrollTop();
const d = $(document).height(); const d = $(document).height();

View file

@ -8,6 +8,7 @@ function initFilters() {
$('#clear_form_filters').on('click', () => { $('#clear_form_filters').on('click', () => {
$('#filters input').val(''); $('#filters input').val('');
$('#filters :checked').removeAttr('checked'); $('#filters :checked').removeAttr('checked');
return false; return false;
}); });
} }
@ -21,4 +22,15 @@ function initExport() {
} }
} }
export { initExport, initFilters }; function initRandom() {
// no display if export (ie: entries) not available
if ($('div').is('#export')) {
$('#button_random').show();
}
}
export {
initExport,
initFilters,
initRandom,
};

View file

@ -1,13 +0,0 @@
<?php
use Composer\Autoload\ClassLoader;
use Doctrine\Common\Annotations\AnnotationRegistry;
/**
* @var ClassLoader
*/
$loader = require __DIR__ . '/../vendor/autoload.php';
AnnotationRegistry::registerLoader([$loader, 'loadClass']);
return $loader;

View file

@ -46,7 +46,6 @@ twig:
doctrine: doctrine:
dbal: dbal:
driver: "%database_driver%" driver: "%database_driver%"
driver_class: "%database_driver_class%"
host: "%database_host%" host: "%database_host%"
port: "%database_port%" port: "%database_port%"
dbname: "%database_name%" dbname: "%database_name%"
@ -55,7 +54,6 @@ doctrine:
charset: "%database_charset%" charset: "%database_charset%"
path: "%database_path%" path: "%database_path%"
unix_socket: "%database_socket%" unix_socket: "%database_socket%"
server_version: 5.6
orm: orm:
auto_generate_proxy_classes: "%kernel.debug%" auto_generate_proxy_classes: "%kernel.debug%"
@ -79,10 +77,13 @@ doctrine_migrations:
# Swiftmailer Configuration # Swiftmailer Configuration
swiftmailer: swiftmailer:
transport: "%mailer_transport%" transport: "%mailer_transport%"
host: "%mailer_host%" username: "%mailer_user%"
username: "%mailer_user%" password: "%mailer_password%"
password: "%mailer_password%" host: "%mailer_host%"
port: "%mailer_port%"
encryption: "%mailer_encryption%"
auth_mode: "%mailer_auth_mode%"
spool: spool:
type: memory type: memory
@ -197,10 +198,17 @@ fos_oauth_server:
refresh_token_lifetime: 1209600 refresh_token_lifetime: 1209600
scheb_two_factor: scheb_two_factor:
trusted_computer: trusted_device:
enabled: true enabled: true
cookie_name: wllbg_trusted_computer cookie_name: wllbg_trusted_computer
cookie_lifetime: 2592000 lifetime: 2592000
backup_codes:
enabled: "%twofactor_auth%"
google:
enabled: "%twofactor_auth%"
template: WallabagUserBundle:Authentication:form.html.twig
email: email:
enabled: "%twofactor_auth%" enabled: "%twofactor_auth%"
@ -357,3 +365,30 @@ jms_serializer:
# see: https://github.com/schmittjoh/JMSSerializerBundle/pull/494 # see: https://github.com/schmittjoh/JMSSerializerBundle/pull/494
datetime: datetime:
default_format: "Y-m-d\\TH:i:sO" # ATOM default_format: "Y-m-d\\TH:i:sO" # ATOM
# see https://github.com/symfony/symfony-standard/pull/1133
sensio_framework_extra:
router:
annotations: false
httplug:
clients:
wallabag_core:
factory: 'wallabag_core.http_client_factory'
config:
defaults:
timeout: 10
plugins: ['httplug.plugin.logger']
wallabag_core.entry.download_images:
factory: 'httplug.factory.auto'
plugins: ['httplug.plugin.logger']
wallabag_import.pocket.client:
factory: 'httplug.factory.auto'
plugins:
- 'httplug.plugin.logger'
- header_defaults:
headers:
'content-type': 'application/json'
'X-Accept': 'application/json'
discovery:
client: false

View file

@ -1,6 +1,7 @@
imports: imports:
- { resource: config_dev.yml } - { resource: config_dev.yml }
- { resource: parameters_test.yml } - { resource: parameters_test.yml }
- { resource: services_test.yml }
framework: framework:
test: ~ test: ~
@ -23,7 +24,6 @@ swiftmailer:
doctrine: doctrine:
dbal: dbal:
driver: "%test_database_driver%" driver: "%test_database_driver%"
driver_class: "%test_database_driver_class%"
host: "%test_database_host%" host: "%test_database_host%"
port: "%test_database_port%" port: "%test_database_port%"
dbname: "%test_database_name%" dbname: "%test_database_name%"

View file

@ -11,8 +11,6 @@ parameters:
# database_password: %env.database_password% # database_password: %env.database_password%
database_driver: pdo_mysql database_driver: pdo_mysql
database_driver_class: ~
# database_driver_class: Wallabag\CoreBundle\Doctrine\DBAL\Driver\CustomPostgreSQLDriver
database_host: 127.0.0.1 database_host: 127.0.0.1
database_port: ~ database_port: ~
database_name: wallabag database_name: wallabag
@ -27,10 +25,13 @@ parameters:
domain_name: https://your-wallabag-url-instance.com domain_name: https://your-wallabag-url-instance.com
mailer_transport: smtp mailer_transport: smtp
mailer_host: 127.0.0.1 mailer_user: ~
mailer_user: ~ mailer_password: ~
mailer_password: ~ mailer_host: 127.0.0.1
mailer_port: false
mailer_encryption: ~
mailer_auth_mode: ~
locale: en locale: en

View file

@ -8,4 +8,3 @@ parameters:
test_database_path: "%env(TEST_DATABASE_PATH)%" test_database_path: "%env(TEST_DATABASE_PATH)%"
env(TEST_DATABASE_PATH): "%kernel.project_dir%/data/db/wallabag_test.sqlite" env(TEST_DATABASE_PATH): "%kernel.project_dir%/data/db/wallabag_test.sqlite"
test_database_charset: utf8 test_database_charset: utf8
test_database_driver_class: ~

View file

@ -51,3 +51,47 @@ craue_config_settings_modify:
fos_js_routing: fos_js_routing:
resource: "@FOSJsRoutingBundle/Resources/config/routing/routing.xml" resource: "@FOSJsRoutingBundle/Resources/config/routing/routing.xml"
2fa_login:
path: /2fa
defaults:
_controller: "scheb_two_factor.form_controller:form"
2fa_login_check:
path: /2fa_check
# redirect RSS feed to Atom
rss_to_atom_unread:
path: /{username}/{token}/unread.xml
defaults:
_controller: FrameworkBundle:Redirect:redirect
route: unread_feed
permanent: true
rss_to_atom_archive:
path: /{username}/{token}/archive.xml
defaults:
_controller: FrameworkBundle:Redirect:redirect
route: archive_feed
permanent: true
rss_to_atom_starred:
path: /{username}/{token}/starred.xml
defaults:
_controller: FrameworkBundle:Redirect:redirect
route: starred_feed
permanent: true
rss_to_atom_all:
path: /{username}/{token}/all.xml
defaults:
_controller: FrameworkBundle:Redirect:redirect
route: all_feed
permanent: true
rss_to_atom_tags:
path: /{username}/{token}/tags/{slug}.xml
defaults:
_controller: FrameworkBundle:Redirect:redirect
route: tag_feed
permanent: true

View file

@ -31,12 +31,15 @@ security:
fos_oauth: true fos_oauth: true
stateless: true stateless: true
anonymous: true anonymous: true
provider: fos_userbundle
login_firewall: login_firewall:
logout_on_user_change: true
pattern: ^/login$ pattern: ^/login$
anonymous: ~ anonymous: ~
secured_area: secured_area:
logout_on_user_change: true
pattern: ^/ pattern: ^/
form_login: form_login:
provider: fos_userbundle provider: fos_userbundle
@ -53,17 +56,27 @@ security:
path: /logout path: /logout
target: / target: /
two_factor:
provider: fos_userbundle
auth_form_path: 2fa_login
check_path: 2fa_login_check
access_control: access_control:
- { path: ^/api/doc, roles: IS_AUTHENTICATED_ANONYMOUSLY } - { path: ^/api/(doc|version|info|user), roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/api/version, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/api/user, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY } - { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
# force role for logout otherwise when 2fa enable, you won't be able to logout
# https://github.com/scheb/two-factor-bundle/issues/168#issuecomment-430822478
- { path: ^/logout, roles: [IS_AUTHENTICATED_ANONYMOUSLY, IS_AUTHENTICATED_2FA_IN_PROGRESS] }
- { path: ^/register, role: IS_AUTHENTICATED_ANONYMOUSLY } - { path: ^/register, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/resetting, role: IS_AUTHENTICATED_ANONYMOUSLY } - { path: ^/resetting, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: /(unread|starred|archive|all).xml$, roles: IS_AUTHENTICATED_ANONYMOUSLY } - { path: /(unread|starred|archive|all).xml$, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/locale, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: /tags/(.*).xml$, roles: IS_AUTHENTICATED_ANONYMOUSLY } - { path: /tags/(.*).xml$, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/feed, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: /(unread|starred|archive).xml$, roles: IS_AUTHENTICATED_ANONYMOUSLY } # For backwards compatibility
- { path: ^/share, roles: IS_AUTHENTICATED_ANONYMOUSLY } - { path: ^/share, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/settings, roles: ROLE_SUPER_ADMIN } - { path: ^/settings, roles: ROLE_SUPER_ADMIN }
- { path: ^/annotations, roles: ROLE_USER } - { path: ^/annotations, roles: ROLE_USER }
- { path: ^/2fa, role: IS_AUTHENTICATED_2FA_IN_PROGRESS }
- { path: ^/users, roles: ROLE_SUPER_ADMIN } - { path: ^/users, roles: ROLE_SUPER_ADMIN }
- { path: ^/, roles: ROLE_USER } - { path: ^/, roles: ROLE_USER }

View file

@ -2,12 +2,6 @@ parameters:
lexik_form_filter.get_filter.doctrine_orm.class: Wallabag\CoreBundle\Event\Subscriber\CustomDoctrineORMSubscriber lexik_form_filter.get_filter.doctrine_orm.class: Wallabag\CoreBundle\Event\Subscriber\CustomDoctrineORMSubscriber
services: services:
# used for tests
filesystem_cache:
class: Doctrine\Common\Cache\FilesystemCache
arguments:
- "%kernel.cache_dir%/doctrine/metadata"
twig.extension.text: twig.extension.text:
class: Twig_Extensions_Extension_Text class: Twig_Extensions_Extension_Text
tags: tags:

View file

@ -0,0 +1,38 @@
services:
# see https://github.com/symfony/symfony/issues/24543
fos_user.user_manager.test:
alias: fos_user.user_manager
public: true
fos_user.security.login_manager.test:
alias: fos_user.security.login_manager
public: true
wallabag_core.entry_repository.test:
alias: wallabag_core.entry_repository
public: true
wallabag_user.user_repository.test:
alias: wallabag_user.user_repository
public: true
filesystem_cache:
class: Doctrine\Common\Cache\FilesystemCache
arguments:
- "%kernel.cache_dir%/doctrine/metadata"
# fixtures
Wallabag\UserBundle\DataFixtures\:
resource: '../../src/Wallabag/UserBundle/DataFixtures/*'
tags: ['doctrine.fixture.orm']
autowire: true
Wallabag\CoreBundle\DataFixtures\:
resource: '../../src/Wallabag/CoreBundle/DataFixtures/*'
tags: ['doctrine.fixture.orm']
autowire: true
Wallabag\AnnotationBundle\DataFixtures\:
resource: '../../src/Wallabag/AnnotationBundle/DataFixtures/*'
tags: ['doctrine.fixture.orm']
autowire: true

View file

@ -1,6 +1,5 @@
parameters: parameters:
test_database_driver: pdo_mysql test_database_driver: pdo_mysql
test_database_driver_class: ~
test_database_host: localhost test_database_host: localhost
test_database_port: 3306 test_database_port: 3306
test_database_name: wallabag_test test_database_name: wallabag_test

View file

@ -1,6 +1,5 @@
parameters: parameters:
test_database_driver: pdo_pgsql test_database_driver: pdo_pgsql
test_database_driver_class: Wallabag\CoreBundle\Doctrine\DBAL\Driver\CustomPostgreSQLDriver
test_database_host: localhost test_database_host: localhost
test_database_port: test_database_port:
test_database_name: wallabag_test test_database_name: wallabag_test

View file

@ -1,6 +1,5 @@
parameters: parameters:
test_database_driver: pdo_sqlite test_database_driver: pdo_sqlite
test_database_driver_class: ~
test_database_host: localhost test_database_host: localhost
test_database_port: test_database_port:
test_database_name: ~ test_database_name: ~

View file

@ -6,19 +6,17 @@ use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Debug\Debug; use Symfony\Component\Debug\Debug;
// if you don't want to setup permissions the proper way, just uncomment the following PHP line // if you don't want to setup permissions the proper way, just uncomment the following PHP line
// read http://symfony.com/doc/current/book/installation.html#configuration-and-setup for more information // read https://symfony.com/doc/current/setup.html#checking-symfony-application-configuration-and-setup
// for more information
//umask(0000); //umask(0000);
set_time_limit(0); set_time_limit(0);
/** require __DIR__.'/../vendor/autoload.php';
* @var Composer\Autoload\ClassLoader $loader
*/
$loader = require __DIR__.'/../app/autoload.php';
$input = new ArgvInput(); $input = new ArgvInput();
$env = $input->getParameterOption(['--env', '-e'], getenv('SYMFONY_ENV') ?: 'dev'); $env = $input->getParameterOption(['--env', '-e'], getenv('SYMFONY_ENV') ?: 'dev', true);
$debug = getenv('SYMFONY_DEBUG') !== '0' && !$input->hasParameterOption(['--no-debug', '']) && $env !== 'prod'; $debug = getenv('SYMFONY_DEBUG') !== '0' && !$input->hasParameterOption('--no-debug', true) && $env !== 'prod';
if ($debug) { if ($debug) {
Debug::enable(); Debug::enable();

View file

@ -2,7 +2,10 @@
"name": "wallabag/wallabag", "name": "wallabag/wallabag",
"type": "project", "type": "project",
"description": "open source self hostable read-it-later web application", "description": "open source self hostable read-it-later web application",
"keywords": ["read-it-later","read it later"], "keywords": [
"read-it-later",
"read it later"
],
"homepage": "https://github.com/wallabag/wallabag", "homepage": "https://github.com/wallabag/wallabag",
"license": "MIT", "license": "MIT",
"authors": [ "authors": [
@ -28,7 +31,7 @@
"issues": "https://github.com/wallabag/wallabag/issues" "issues": "https://github.com/wallabag/wallabag/issues"
}, },
"require": { "require": {
"php": ">=5.6.0", "php": ">=7.1.3",
"ext-pcre": "*", "ext-pcre": "*",
"ext-dom": "*", "ext-dom": "*",
"ext-curl": "*", "ext-curl": "*",
@ -44,59 +47,68 @@
"ext-tokenizer": "*", "ext-tokenizer": "*",
"ext-pdo": "*", "ext-pdo": "*",
"ext-tidy": "*", "ext-tidy": "*",
"symfony/symfony": "~3.3.13", "symfony/symfony": "3.4.*",
"doctrine/orm": "^2.5.12", "doctrine/orm": "^2.6",
"doctrine/doctrine-bundle": "^1.8.0", "doctrine/doctrine-bundle": "^1.9",
"doctrine/doctrine-cache-bundle": "^1.3.2", "doctrine/doctrine-cache-bundle": "^1.3",
"twig/extensions": "^1.5.1", "twig/extensions": "^1.5",
"symfony/swiftmailer-bundle": "^2.6.7", "symfony/swiftmailer-bundle": "^3.2",
"symfony/monolog-bundle": "^3.1.2", "symfony/monolog-bundle": "^3.1",
"sensio/distribution-bundle": "^5.0.21", "sensio/distribution-bundle": "^5.0",
"sensio/framework-extra-bundle": "^3.0.28", "sensio/framework-extra-bundle": "^5.2",
"incenteev/composer-parameter-handler": "^2.1.2", "incenteev/composer-parameter-handler": "^2.1",
"nelmio/cors-bundle": "~1.5", "nelmio/cors-bundle": "~1.5",
"friendsofsymfony/rest-bundle": "~2.1", "friendsofsymfony/rest-bundle": "~2.1",
"jms/serializer-bundle": "~2.2", "jms/serializer-bundle": "~2.2",
"nelmio/api-doc-bundle": "^2.13.2", "nelmio/api-doc-bundle": "^2.13.2",
"mgargano/simplehtmldom": "~1.5", "mgargano/simplehtmldom": "~1.5",
"wallabag/tcpdf": "^6.2.15", "wallabag/tcpdf": "^6.2.26",
"simplepie/simplepie": "~1.5", "simplepie/simplepie": "~1.5",
"willdurand/hateoas-bundle": "~1.3", "willdurand/hateoas-bundle": "~1.3",
"liip/theme-bundle": "^1.4.6", "liip/theme-bundle": "^1.4.6",
"lexik/form-filter-bundle": "^5.0.4", "lexik/form-filter-bundle": "^5.0.4",
"j0k3r/graby": "^1.0", "j0k3r/graby": "^2.0",
"php-http/guzzle5-adapter": "^2.0",
"friendsofsymfony/user-bundle": "2.0.*", "friendsofsymfony/user-bundle": "2.0.*",
"friendsofsymfony/oauth-server-bundle": "^1.5.2", "friendsofsymfony/oauth-server-bundle": "^1.5",
"stof/doctrine-extensions-bundle": "^1.2", "stof/doctrine-extensions-bundle": "^1.2",
"scheb/two-factor-bundle": "^2.14.0", "scheb/two-factor-bundle": "^3.0",
"grandt/phpepub": "^4.0.7", "grandt/phpepub": "dev-master",
"wallabag/php-mobi": "~1.0.0", "wallabag/php-mobi": "~1.0",
"kphoen/rulerz-bundle": "~0.13", "kphoen/rulerz-bundle": "~0.13",
"guzzlehttp/guzzle": "^5.3.1", "guzzlehttp/guzzle": "^5.3.1",
"doctrine/doctrine-migrations-bundle": "^1.3", "doctrine/doctrine-migrations-bundle": "^1.3",
"paragonie/random_compat": "^2.0.11", "craue/config-bundle": "dev-utf8mb4",
"craue/config-bundle": "~2.0",
"mnapoli/piwik-twig-extension": "^1.0", "mnapoli/piwik-twig-extension": "^1.0",
"ocramius/proxy-manager": "^1.0.2", "ocramius/proxy-manager": "^2.1.1",
"white-october/pagerfanta-bundle": "^1.1.0", "white-october/pagerfanta-bundle": "^1.1",
"php-amqplib/rabbitmq-bundle": "^1.14", "php-amqplib/rabbitmq-bundle": "^1.14",
"predis/predis": "^1.1.1", "predis/predis": "v1.1.x-dev",
"javibravo/simpleue": "^2.0", "javibravo/simpleue": "^2.0",
"symfony/dom-crawler": "^3.3.13", "symfony/dom-crawler": "^3.4",
"friendsofsymfony/jsrouting-bundle": "^1.6.3", "friendsofsymfony/jsrouting-bundle": "^2.2",
"bdunogier/guzzle-site-authenticator": "^1.0.0", "bdunogier/guzzle-site-authenticator": "^1.0.0",
"defuse/php-encryption": "^2.1", "defuse/php-encryption": "^2.1",
"html2text/html2text": "^4.1", "html2text/html2text": "^4.1",
"sulu/symfony-intl-fix": "^1.0" "pragmarx/recovery": "^0.1.0",
"php-http/httplug-bundle": "^1.14"
}, },
"require-dev": { "require-dev": {
"doctrine/doctrine-fixtures-bundle": "~2.2", "doctrine/doctrine-fixtures-bundle": "~3.0",
"doctrine/data-fixtures": "~1.1",
"sensio/generator-bundle": "^3.0", "sensio/generator-bundle": "^3.0",
"symfony/phpunit-bridge": "^4.2", "symfony/phpunit-bridge": "^4.2",
"friendsofphp/php-cs-fixer": "~2.0", "friendsofphp/php-cs-fixer": "~2.13",
"m6web/redis-mock": "^2.0", "m6web/redis-mock": "^4.1",
"dama/doctrine-test-bundle": "^4.0" "dama/doctrine-test-bundle": "^5.0",
"phpstan/phpstan": "^0.11.0",
"phpstan/phpstan-phpunit": "^0.11.0",
"phpstan/phpstan-symfony": "^0.11.0",
"phpstan/phpstan-doctrine": "^0.11.0",
"php-http/mock-client": "^1.0",
"guzzlehttp/psr7": "^1.0"
},
"suggest": {
"ext-imagick": "To keep GIF animation when downloading image is enabled"
}, },
"scripts": { "scripts": {
"post-cmd": [ "post-cmd": [
@ -125,22 +137,40 @@
} }
}, },
"autoload": { "autoload": {
"psr-4": { "Wallabag\\": "src/Wallabag/" }, "psr-4": {
"classmap": [ "app/AppKernel.php", "app/AppCache.php" ], "Wallabag\\": "src/Wallabag/"
"exclude-from-classmap": [ },
"vendor/symfony/intl/Locale.php", "classmap": [
"vendor/symfony/symfony/src/Symfony/Component/Intl/Locale.php" "app/AppKernel.php",
"app/AppCache.php"
] ]
}, },
"autoload-dev": { "autoload-dev": {
"psr-4": { "Tests\\": "tests/" } "psr-4": {
"Tests\\": "tests/"
},
"files": [
"vendor/symfony/symfony/src/Symfony/Component/VarDumper/Resources/functions/dump.php"
]
}, },
"config": { "config": {
"bin-dir": "bin", "bin-dir": "bin",
"platform": { "platform": {
"php": "5.6.0" "php": "7.1.3"
} }
}, },
"minimum-stability": "dev", "minimum-stability": "dev",
"prefer-stable": true "prefer-stable": true,
"repositories": [
{
"type": "vcs",
"url": "https://github.com/Daniel-KM/PHPePub",
"comment": "The most up-to-date PHPePub as of now"
},
{
"type": "vcs",
"url": "https://github.com/wallabag/CraueConfigBundle",
"comment": "To handle utf8mb4 field size"
}
]
} }

View file

@ -1,4 +1,4 @@
FROM php:fpm FROM php:7.2-fpm
# Default timezone. To change it, use the argument in the docker-compose.yml file # Default timezone. To change it, use the argument in the docker-compose.yml file
ARG timezone='Europe/Paris' ARG timezone='Europe/Paris'

13
phpstan.neon Normal file
View file

@ -0,0 +1,13 @@
includes:
- vendor/phpstan/phpstan-phpunit/extension.neon
- vendor/phpstan/phpstan-symfony/extension.neon
- vendor/phpstan/phpstan-doctrine/extension.neon
- vendor/phpstan/phpstan-phpunit/rules.neon
parameters:
symfony:
container_xml_path: %rootDir%/../../../var/cache/test/appTestDebugProjectContainer.xml
# https://github.com/phpstan/phpstan/issues/694#issuecomment-350724288
autoload_files:
- vendor/bin/.phpunit/phpunit-6.5/vendor/autoload.php

View file

@ -4,7 +4,7 @@
xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.8/phpunit.xsd" xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.8/phpunit.xsd"
backupGlobals="false" backupGlobals="false"
colors="true" colors="true"
bootstrap="app/autoload.php" bootstrap="vendor/autoload.php"
> >
<testsuites> <testsuites>
@ -15,7 +15,7 @@
<php> <php>
<ini name="error_reporting" value="-1" /> <ini name="error_reporting" value="-1" />
<server name="KERNEL_DIR" value="app/" /> <server name="KERNEL_CLASS" value="AppKernel" />
<env name="SYMFONY_DEPRECATIONS_HELPER" value="weak" /> <env name="SYMFONY_DEPRECATIONS_HELPER" value="weak" />
</php> </php>

View file

@ -1,13 +1,15 @@
<?php <?php
namespace Wallabag\AnnotationBundle\DataFixtures\ORM; namespace Wallabag\AnnotationBundle\DataFixtures;
use Doctrine\Common\DataFixtures\AbstractFixture; use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Common\DataFixtures\OrderedFixtureInterface; use Doctrine\Common\DataFixtures\DependentFixtureInterface;
use Doctrine\Common\Persistence\ObjectManager; use Doctrine\Common\Persistence\ObjectManager;
use Wallabag\AnnotationBundle\Entity\Annotation; use Wallabag\AnnotationBundle\Entity\Annotation;
use Wallabag\CoreBundle\DataFixtures\EntryFixtures;
use Wallabag\UserBundle\DataFixtures\UserFixtures;
class LoadAnnotationData extends AbstractFixture implements OrderedFixtureInterface class AnnotationFixtures extends Fixture implements DependentFixtureInterface
{ {
/** /**
* {@inheritdoc} * {@inheritdoc}
@ -38,8 +40,11 @@ class LoadAnnotationData extends AbstractFixture implements OrderedFixtureInterf
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
public function getOrder() public function getDependencies()
{ {
return 35; return [
EntryFixtures::class,
UserFixtures::class,
];
} }
} }

View file

@ -2,9 +2,9 @@
namespace Wallabag\ApiBundle\Controller; namespace Wallabag\ApiBundle\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
use Wallabag\ApiBundle\Entity\Client; use Wallabag\ApiBundle\Entity\Client;
use Wallabag\ApiBundle\Form\Type\ClientType; use Wallabag\ApiBundle\Form\Type\ClientType;

View file

@ -4,18 +4,17 @@ namespace Wallabag\ApiBundle\Controller;
use Hateoas\Configuration\Route; use Hateoas\Configuration\Route;
use Hateoas\Representation\Factory\PagerfantaFactory; use Hateoas\Representation\Factory\PagerfantaFactory;
use JMS\Serializer\SerializationContext;
use Nelmio\ApiDocBundle\Annotation\ApiDoc; use Nelmio\ApiDocBundle\Annotation\ApiDoc;
use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\HttpKernel\Exception\HttpException; use Symfony\Component\HttpKernel\Exception\HttpException;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Wallabag\CoreBundle\Entity\Entry; use Wallabag\CoreBundle\Entity\Entry;
use Wallabag\CoreBundle\Entity\Tag; use Wallabag\CoreBundle\Entity\Tag;
use Wallabag\CoreBundle\Event\EntryDeletedEvent; use Wallabag\CoreBundle\Event\EntryDeletedEvent;
use Wallabag\CoreBundle\Event\EntrySavedEvent; use Wallabag\CoreBundle\Event\EntrySavedEvent;
use Wallabag\CoreBundle\Helper\UrlHasher;
class EntryRestController extends WallabagRestController class EntryRestController extends WallabagRestController
{ {
@ -29,8 +28,10 @@ class EntryRestController extends WallabagRestController
* @ApiDoc( * @ApiDoc(
* parameters={ * parameters={
* {"name"="return_id", "dataType"="string", "required"=false, "format"="1 or 0", "description"="Set 1 if you want to retrieve ID in case entry(ies) exists, 0 by default"}, * {"name"="return_id", "dataType"="string", "required"=false, "format"="1 or 0", "description"="Set 1 if you want to retrieve ID in case entry(ies) exists, 0 by default"},
* {"name"="url", "dataType"="string", "required"=true, "format"="An url", "description"="Url to check if it exists"}, * {"name"="url", "dataType"="string", "required"=true, "format"="An url", "description"="DEPRECATED, use hashed_url instead"},
* {"name"="urls", "dataType"="string", "required"=false, "format"="An array of urls (?urls[]=http...&urls[]=http...)", "description"="Urls (as an array) to check if it exists"} * {"name"="urls", "dataType"="string", "required"=false, "format"="An array of urls (?urls[]=http...&urls[]=http...)", "description"="DEPRECATED, use hashed_urls instead"},
* {"name"="hashed_url", "dataType"="string", "required"=false, "format"="A hashed url", "description"="Hashed url using SHA1 to check if it exists"},
* {"name"="hashed_urls", "dataType"="string", "required"=false, "format"="An array of hashed urls (?hashed_urls[]=xxx...&hashed_urls[]=xxx...)", "description"="An array of hashed urls using SHA1 to check if they exist"}
* } * }
* ) * )
* *
@ -39,38 +40,49 @@ class EntryRestController extends WallabagRestController
public function getEntriesExistsAction(Request $request) public function getEntriesExistsAction(Request $request)
{ {
$this->validateAuthentication(); $this->validateAuthentication();
$repo = $this->getDoctrine()->getRepository('WallabagCoreBundle:Entry');
$returnId = (null === $request->query->get('return_id')) ? false : (bool) $request->query->get('return_id'); $returnId = (null === $request->query->get('return_id')) ? false : (bool) $request->query->get('return_id');
$urls = $request->query->get('urls', []);
// handle multiple urls first $hashedUrls = $request->query->get('hashed_urls', []);
if (!empty($urls)) { $hashedUrl = $request->query->get('hashed_url', '');
$results = []; if (!empty($hashedUrl)) {
foreach ($urls as $url) { $hashedUrls[] = $hashedUrl;
$res = $this->getDoctrine()
->getRepository('WallabagCoreBundle:Entry')
->findByUrlAndUserId($url, $this->getUser()->getId());
$results[$url] = $this->returnExistInformation($res, $returnId);
}
return $this->sendResponse($results);
} }
// let's see if it is a simple url? $urls = $request->query->get('urls', []);
$url = $request->query->get('url', ''); $url = $request->query->get('url', '');
if (!empty($url)) {
$urls[] = $url;
}
if (empty($url)) { $urlHashMap = [];
foreach ($urls as $urlToHash) {
$urlHash = UrlHasher::hashUrl($urlToHash);
$hashedUrls[] = $urlHash;
$urlHashMap[$urlHash] = $urlToHash;
}
if (empty($hashedUrls)) {
throw $this->createAccessDeniedException('URL is empty?, logged user id: ' . $this->getUser()->getId()); throw $this->createAccessDeniedException('URL is empty?, logged user id: ' . $this->getUser()->getId());
} }
$res = $this->getDoctrine() $results = [];
->getRepository('WallabagCoreBundle:Entry') foreach ($hashedUrls as $hashedUrlToSearch) {
->findByUrlAndUserId($url, $this->getUser()->getId()); $res = $repo->findByHashedUrlAndUserId($hashedUrlToSearch, $this->getUser()->getId());
$exists = $this->returnExistInformation($res, $returnId); $results[$hashedUrlToSearch] = $this->returnExistInformation($res, $returnId);
}
return $this->sendResponse(['exists' => $exists]); $results = $this->replaceUrlHashes($results, $urlHashMap);
if (!empty($url) || !empty($hashedUrl)) {
$hu = array_keys($results)[0];
return $this->sendResponse(['exists' => $results[$hu]]);
}
return $this->sendResponse($results);
} }
/** /**
@ -80,13 +92,14 @@ class EntryRestController extends WallabagRestController
* parameters={ * parameters={
* {"name"="archive", "dataType"="integer", "required"=false, "format"="1 or 0, all entries by default", "description"="filter by archived status."}, * {"name"="archive", "dataType"="integer", "required"=false, "format"="1 or 0, all entries by default", "description"="filter by archived status."},
* {"name"="starred", "dataType"="integer", "required"=false, "format"="1 or 0, all entries by default", "description"="filter by starred status."}, * {"name"="starred", "dataType"="integer", "required"=false, "format"="1 or 0, all entries by default", "description"="filter by starred status."},
* {"name"="sort", "dataType"="string", "required"=false, "format"="'created' or 'updated', default 'created'", "description"="sort entries by date."}, * {"name"="sort", "dataType"="string", "required"=false, "format"="'created' or 'updated' or 'archived', default 'created'", "description"="sort entries by date."},
* {"name"="order", "dataType"="string", "required"=false, "format"="'asc' or 'desc', default 'desc'", "description"="order of sort."}, * {"name"="order", "dataType"="string", "required"=false, "format"="'asc' or 'desc', default 'desc'", "description"="order of sort."},
* {"name"="page", "dataType"="integer", "required"=false, "format"="default '1'", "description"="what page you want."}, * {"name"="page", "dataType"="integer", "required"=false, "format"="default '1'", "description"="what page you want."},
* {"name"="perPage", "dataType"="integer", "required"=false, "format"="default'30'", "description"="results per page."}, * {"name"="perPage", "dataType"="integer", "required"=false, "format"="default'30'", "description"="results per page."},
* {"name"="tags", "dataType"="string", "required"=false, "format"="api,rest", "description"="a list of tags url encoded. Will returns entries that matches ALL tags."}, * {"name"="tags", "dataType"="string", "required"=false, "format"="api,rest", "description"="a list of tags url encoded. Will returns entries that matches ALL tags."},
* {"name"="since", "dataType"="integer", "required"=false, "format"="default '0'", "description"="The timestamp since when you want entries updated."}, * {"name"="since", "dataType"="integer", "required"=false, "format"="default '0'", "description"="The timestamp since when you want entries updated."},
* {"name"="public", "dataType"="integer", "required"=false, "format"="1 or 0, all entries by default", "description"="filter by entries with a public link"}, * {"name"="public", "dataType"="integer", "required"=false, "format"="1 or 0, all entries by default", "description"="filter by entries with a public link"},
* {"name"="detail", "dataType"="string", "required"=false, "format"="metadata or full, metadata by default", "description"="include content field if 'full'. 'full' by default for backward compatibility."},
* } * }
* ) * )
* *
@ -105,6 +118,7 @@ class EntryRestController extends WallabagRestController
$perPage = (int) $request->query->get('perPage', 30); $perPage = (int) $request->query->get('perPage', 30);
$tags = \is_array($request->query->get('tags')) ? '' : (string) $request->query->get('tags', ''); $tags = \is_array($request->query->get('tags')) ? '' : (string) $request->query->get('tags', '');
$since = $request->query->get('since', 0); $since = $request->query->get('since', 0);
$detail = strtolower($request->query->get('detail', 'full'));
try { try {
/** @var \Pagerfanta\Pagerfanta $pager */ /** @var \Pagerfanta\Pagerfanta $pager */
@ -116,7 +130,8 @@ class EntryRestController extends WallabagRestController
$sort, $sort,
$order, $order,
$since, $since,
$tags $tags,
$detail
); );
} catch (\Exception $e) { } catch (\Exception $e) {
throw new BadRequestHttpException($e->getMessage()); throw new BadRequestHttpException($e->getMessage());
@ -140,8 +155,9 @@ class EntryRestController extends WallabagRestController
'perPage' => $perPage, 'perPage' => $perPage,
'tags' => $tags, 'tags' => $tags,
'since' => $since, 'since' => $since,
'detail' => $detail,
], ],
UrlGeneratorInterface::ABSOLUTE_URL true
) )
); );
@ -349,9 +365,7 @@ class EntryRestController extends WallabagRestController
'language' => !empty($data['language']) ? $data['language'] : $entry->getLanguage(), 'language' => !empty($data['language']) ? $data['language'] : $entry->getLanguage(),
'date' => !empty($data['publishedAt']) ? $data['publishedAt'] : $entry->getPublishedAt(), 'date' => !empty($data['publishedAt']) ? $data['publishedAt'] : $entry->getPublishedAt(),
// faking the open graph preview picture // faking the open graph preview picture
'open_graph' => [ 'image' => !empty($data['picture']) ? $data['picture'] : $entry->getPreviewPicture(),
'og_image' => !empty($data['picture']) ? $data['picture'] : $entry->getPreviewPicture(),
],
'authors' => \is_string($data['authors']) ? explode(',', $data['authors']) : $entry->getPublishedBy(), 'authors' => \is_string($data['authors']) ? explode(',', $data['authors']) : $entry->getPublishedBy(),
] ]
); );
@ -363,7 +377,7 @@ class EntryRestController extends WallabagRestController
} }
if (null !== $data['isArchived']) { if (null !== $data['isArchived']) {
$entry->setArchived((bool) $data['isArchived']); $entry->updateArchived((bool) $data['isArchived']);
} }
if (null !== $data['isStarred']) { if (null !== $data['isStarred']) {
@ -479,7 +493,7 @@ class EntryRestController extends WallabagRestController
} }
if (null !== $data['isArchived']) { if (null !== $data['isArchived']) {
$entry->setArchived((bool) $data['isArchived']); $entry->updateArchived((bool) $data['isArchived']);
} }
if (null !== $data['isStarred']) { if (null !== $data['isStarred']) {
@ -787,21 +801,21 @@ class EntryRestController extends WallabagRestController
} }
/** /**
* Shortcut to send data serialized in json. * Replace the hashedUrl keys in $results with the unhashed URL from the
* * request, as recorded in $urlHashMap.
* @param mixed $data
*
* @return JsonResponse
*/ */
private function sendResponse($data) private function replaceUrlHashes(array $results, array $urlHashMap)
{ {
// https://github.com/schmittjoh/JMSSerializerBundle/issues/293 $newResults = [];
$context = new SerializationContext(); foreach ($results as $hash => $res) {
$context->setSerializeNull(true); if (isset($urlHashMap[$hash])) {
$newResults[$urlHashMap[$hash]] = $res;
} else {
$newResults[$hash] = $res;
}
}
$json = $this->get('jms_serializer')->serialize($data, 'json', $context); return $newResults;
return (new JsonResponse())->setJson($json);
} }
/** /**
@ -832,8 +846,8 @@ class EntryRestController extends WallabagRestController
/** /**
* Return information about the entry if it exist and depending on the id or not. * Return information about the entry if it exist and depending on the id or not.
* *
* @param Entry|null $entry * @param Entry|bool|null $entry
* @param bool $returnId * @param bool $returnId
* *
* @return bool|int * @return bool|int
*/ */

View file

@ -0,0 +1,65 @@
<?php
namespace Wallabag\ApiBundle\Controller;
use Hateoas\Configuration\Route;
use Hateoas\Representation\Factory\PagerfantaFactory;
use Nelmio\ApiDocBundle\Annotation\ApiDoc;
use Pagerfanta\Adapter\DoctrineORMAdapter;
use Pagerfanta\Pagerfanta;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
class SearchRestController extends WallabagRestController
{
/**
* Search all entries by term.
*
* @ApiDoc(
* parameters={
* {"name"="term", "dataType"="string", "required"=false, "format"="any", "description"="Any query term"},
* {"name"="page", "dataType"="integer", "required"=false, "format"="default '1'", "description"="what page you want."},
* {"name"="perPage", "dataType"="integer", "required"=false, "format"="default'30'", "description"="results per page."}
* }
* )
*
* @return JsonResponse
*/
public function getSearchAction(Request $request)
{
$this->validateAuthentication();
$term = $request->query->get('term');
$page = (int) $request->query->get('page', 1);
$perPage = (int) $request->query->get('perPage', 30);
$qb = $this->get('wallabag_core.entry_repository')
->getBuilderForSearchByUser(
$this->getUser()->getId(),
$term,
null
);
$pagerAdapter = new DoctrineORMAdapter($qb->getQuery(), true, false);
$pager = new Pagerfanta($pagerAdapter);
$pager->setMaxPerPage($perPage);
$pager->setCurrentPage($page);
$pagerfantaFactory = new PagerfantaFactory('page', 'perPage');
$paginatedCollection = $pagerfantaFactory->createRepresentation(
$pager,
new Route(
'api_get_search',
[
'term' => $term,
'page' => $page,
'perPage' => $perPage,
],
true
)
);
return $this->sendResponse($paginatedCollection);
}
}

View file

@ -3,6 +3,7 @@
namespace Wallabag\ApiBundle\Controller; namespace Wallabag\ApiBundle\Controller;
use FOS\RestBundle\Controller\FOSRestController; use FOS\RestBundle\Controller\FOSRestController;
use JMS\Serializer\SerializationContext;
use Nelmio\ApiDocBundle\Annotation\ApiDoc; use Nelmio\ApiDocBundle\Annotation\ApiDoc;
use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Security\Core\Exception\AccessDeniedException; use Symfony\Component\Security\Core\Exception\AccessDeniedException;
@ -14,6 +15,8 @@ class WallabagRestController extends FOSRestController
* *
* @ApiDoc() * @ApiDoc()
* *
* @deprecated Should use info endpoint instead
*
* @return JsonResponse * @return JsonResponse
*/ */
public function getVersionAction() public function getVersionAction()
@ -24,6 +27,24 @@ class WallabagRestController extends FOSRestController
return (new JsonResponse())->setJson($json); return (new JsonResponse())->setJson($json);
} }
/**
* Retrieve information about the wallabag instance.
*
* @ApiDoc()
*
* @return JsonResponse
*/
public function getInfoAction()
{
$info = [
'appname' => 'wallabag',
'version' => $this->container->getParameter('wallabag_core.version'),
'allowed_registration' => $this->container->getParameter('wallabag_user.registration_enabled'),
];
return (new JsonResponse())->setJson($this->get('jms_serializer')->serialize($info, 'json'));
}
protected function validateAuthentication() protected function validateAuthentication()
{ {
if (false === $this->get('security.authorization_checker')->isGranted('IS_AUTHENTICATED_FULLY')) { if (false === $this->get('security.authorization_checker')->isGranted('IS_AUTHENTICATED_FULLY')) {
@ -44,4 +65,22 @@ class WallabagRestController extends FOSRestController
throw $this->createAccessDeniedException('Access forbidden. Entry user id: ' . $requestUserId . ', logged user id: ' . $user->getId()); throw $this->createAccessDeniedException('Access forbidden. Entry user id: ' . $requestUserId . ', logged user id: ' . $user->getId());
} }
} }
/**
* Shortcut to send data serialized in json.
*
* @param mixed $data
*
* @return JsonResponse
*/
protected function sendResponse($data)
{
// https://github.com/schmittjoh/JMSSerializerBundle/issues/293
$context = new SerializationContext();
$context->setSerializeNull(true);
$json = $this->get('jms_serializer')->serialize($data, 'json', $context);
return (new JsonResponse())->setJson($json);
}
} }

View file

@ -8,6 +8,22 @@ use FOS\OAuthServerBundle\Entity\AccessToken as BaseAccessToken;
/** /**
* @ORM\Table("oauth2_access_tokens") * @ORM\Table("oauth2_access_tokens")
* @ORM\Entity * @ORM\Entity
* @ORM\AttributeOverrides({
* @ORM\AttributeOverride(name="token",
* column=@ORM\Column(
* name = "token",
* type = "string",
* length = 191
* )
* ),
* @ORM\AttributeOverride(name="scope",
* column=@ORM\Column(
* name = "scope",
* type = "string",
* length = 191
* )
* )
* })
*/ */
class AccessToken extends BaseAccessToken class AccessToken extends BaseAccessToken
{ {
@ -26,6 +42,7 @@ class AccessToken extends BaseAccessToken
/** /**
* @ORM\ManyToOne(targetEntity="Wallabag\UserBundle\Entity\User") * @ORM\ManyToOne(targetEntity="Wallabag\UserBundle\Entity\User")
* @ORM\JoinColumn(name="user_id", referencedColumnName="id", onDelete="CASCADE")
*/ */
protected $user; protected $user;
} }

View file

@ -8,6 +8,22 @@ use FOS\OAuthServerBundle\Entity\AuthCode as BaseAuthCode;
/** /**
* @ORM\Table("oauth2_auth_codes") * @ORM\Table("oauth2_auth_codes")
* @ORM\Entity * @ORM\Entity
* @ORM\AttributeOverrides({
* @ORM\AttributeOverride(name="token",
* column=@ORM\Column(
* name = "token",
* type = "string",
* length = 191
* )
* ),
* @ORM\AttributeOverride(name="scope",
* column=@ORM\Column(
* name = "scope",
* type = "string",
* length = 191
* )
* )
* })
*/ */
class AuthCode extends BaseAuthCode class AuthCode extends BaseAuthCode
{ {
@ -26,6 +42,7 @@ class AuthCode extends BaseAuthCode
/** /**
* @ORM\ManyToOne(targetEntity="Wallabag\UserBundle\Entity\User") * @ORM\ManyToOne(targetEntity="Wallabag\UserBundle\Entity\User")
* @ORM\JoinColumn(name="user_id", referencedColumnName="id", onDelete="CASCADE")
*/ */
protected $user; protected $user;
} }

View file

@ -8,6 +8,22 @@ use FOS\OAuthServerBundle\Entity\RefreshToken as BaseRefreshToken;
/** /**
* @ORM\Table("oauth2_refresh_tokens") * @ORM\Table("oauth2_refresh_tokens")
* @ORM\Entity * @ORM\Entity
* @ORM\AttributeOverrides({
* @ORM\AttributeOverride(name="token",
* column=@ORM\Column(
* name = "token",
* type = "string",
* length = 191
* )
* ),
* @ORM\AttributeOverride(name="scope",
* column=@ORM\Column(
* name = "scope",
* type = "string",
* length = 191
* )
* )
* })
*/ */
class RefreshToken extends BaseRefreshToken class RefreshToken extends BaseRefreshToken
{ {
@ -26,6 +42,7 @@ class RefreshToken extends BaseRefreshToken
/** /**
* @ORM\ManyToOne(targetEntity="Wallabag\UserBundle\Entity\User") * @ORM\ManyToOne(targetEntity="Wallabag\UserBundle\Entity\User")
* @ORM\JoinColumn(name="user_id", referencedColumnName="id", onDelete="CASCADE")
*/ */
protected $user; protected $user;
} }

View file

@ -20,6 +20,7 @@ class ClientType extends AbstractType
'required' => false, 'required' => false,
'label' => 'developer.client.form.redirect_uris_label', 'label' => 'developer.client.form.redirect_uris_label',
'property_path' => 'redirectUris', 'property_path' => 'redirectUris',
'default_protocol' => null,
]) ])
->add('save', SubmitType::class, ['label' => 'developer.client.form.save_label']) ->add('save', SubmitType::class, ['label' => 'developer.client.form.save_label'])
; ;

View file

@ -3,6 +3,11 @@ entry:
resource: "WallabagApiBundle:EntryRest" resource: "WallabagApiBundle:EntryRest"
name_prefix: api_ name_prefix: api_
search:
type: rest
resource: "WallabagApiBundle:SearchRest"
name_prefix: api_
tag: tag:
type: rest type: rest
resource: "WallabagApiBundle:TagRest" resource: "WallabagApiBundle:TagRest"

View file

@ -0,0 +1,99 @@
<?php
namespace Wallabag\CoreBundle\Command;
use Doctrine\ORM\NoResultException;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Wallabag\CoreBundle\Helper\UrlHasher;
use Wallabag\UserBundle\Entity\User;
class GenerateUrlHashesCommand extends ContainerAwareCommand
{
/** @var OutputInterface */
protected $output;
protected function configure()
{
$this
->setName('wallabag:generate-hashed-urls')
->setDescription('Generates hashed urls for each entry')
->setHelp('This command helps you to generates hashes of the url of each entry, to check through API if an URL is already saved')
->addArgument('username', InputArgument::OPTIONAL, 'User to process entries');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$this->output = $output;
$username = (string) $input->getArgument('username');
if ($username) {
try {
$user = $this->getUser($username);
$this->generateHashedUrls($user);
} catch (NoResultException $e) {
$output->writeln(sprintf('<error>User "%s" not found.</error>', $username));
return 1;
}
} else {
$users = $this->getDoctrine()->getRepository('WallabagUserBundle:User')->findAll();
$output->writeln(sprintf('Generating hashed urls for "%d" users', \count($users)));
foreach ($users as $user) {
$output->writeln(sprintf('Processing user: %s', $user->getUsername()));
$this->generateHashedUrls($user);
}
$output->writeln('Finished generated hashed urls');
}
return 0;
}
/**
* @param User $user
*/
private function generateHashedUrls(User $user)
{
$em = $this->getContainer()->get('doctrine.orm.entity_manager');
$repo = $this->getDoctrine()->getRepository('WallabagCoreBundle:Entry');
$entries = $repo->findByUser($user->getId());
$i = 1;
foreach ($entries as $entry) {
$entry->setHashedUrl(UrlHasher::hashUrl($entry->getUrl()));
$em->persist($entry);
if (0 === ($i % 20)) {
$em->flush();
}
++$i;
}
$em->flush();
$this->output->writeln(sprintf('Generated hashed urls for user: %s', $user->getUserName()));
}
/**
* Fetches a user from its username.
*
* @param string $username
*
* @return User
*/
private function getUser($username)
{
return $this->getDoctrine()->getRepository('WallabagUserBundle:User')->findOneByUserName($username);
}
private function getDoctrine()
{
return $this->getContainer()->get('doctrine');
}
}

View file

@ -94,8 +94,9 @@ class InstallCommand extends ContainerAwareCommand
$status = '<info>OK!</info>'; $status = '<info>OK!</info>';
$help = ''; $help = '';
$conn = $this->getContainer()->get('doctrine')->getManager()->getConnection();
try { try {
$conn = $this->getContainer()->get('doctrine')->getManager()->getConnection();
$conn->connect(); $conn->connect();
} catch (\Exception $e) { } catch (\Exception $e) {
if (false === strpos($e->getMessage(), 'Unknown database') if (false === strpos($e->getMessage(), 'Unknown database')
@ -253,7 +254,7 @@ class InstallCommand extends ContainerAwareCommand
$question->setHidden(true); $question->setHidden(true);
$user->setPlainPassword($this->io->askQuestion($question)); $user->setPlainPassword($this->io->askQuestion($question));
$user->setEmail($this->io->ask('Email', '')); $user->setEmail($this->io->ask('Email', 'wallabag@wallabag.io'));
$user->setEnabled(true); $user->setEnabled(true);
$user->addRole('ROLE_SUPER_ADMIN'); $user->addRole('ROLE_SUPER_ADMIN');

View file

@ -57,7 +57,8 @@ class ShowUserCommand extends ContainerAwareCommand
sprintf('Display name: %s', $user->getName()), sprintf('Display name: %s', $user->getName()),
sprintf('Creation date: %s', $user->getCreatedAt()->format('Y-m-d H:i:s')), sprintf('Creation date: %s', $user->getCreatedAt()->format('Y-m-d H:i:s')),
sprintf('Last login: %s', null !== $user->getLastLogin() ? $user->getLastLogin()->format('Y-m-d H:i:s') : 'never'), sprintf('Last login: %s', null !== $user->getLastLogin() ? $user->getLastLogin()->format('Y-m-d H:i:s') : 'never'),
sprintf('2FA activated: %s', $user->isTwoFactorAuthentication() ? 'yes' : 'no'), sprintf('2FA (email) activated: %s', $user->isEmailTwoFactor() ? 'yes' : 'no'),
sprintf('2FA (OTP) activated: %s', $user->isGoogleAuthenticatorEnabled() ? 'yes' : 'no'),
]); ]);
} }

View file

@ -2,17 +2,19 @@
namespace Wallabag\CoreBundle\Controller; namespace Wallabag\CoreBundle\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; use PragmaRX\Recovery\Recovery as BackupCodes;
use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Validator\Constraints\Locale as LocaleConstraint;
use Wallabag\CoreBundle\Entity\Config; use Wallabag\CoreBundle\Entity\Config;
use Wallabag\CoreBundle\Entity\TaggingRule; use Wallabag\CoreBundle\Entity\TaggingRule;
use Wallabag\CoreBundle\Form\Type\ChangePasswordType; use Wallabag\CoreBundle\Form\Type\ChangePasswordType;
use Wallabag\CoreBundle\Form\Type\ConfigType; use Wallabag\CoreBundle\Form\Type\ConfigType;
use Wallabag\CoreBundle\Form\Type\RssType; use Wallabag\CoreBundle\Form\Type\FeedType;
use Wallabag\CoreBundle\Form\Type\TaggingRuleType; use Wallabag\CoreBundle\Form\Type\TaggingRuleType;
use Wallabag\CoreBundle\Form\Type\UserInformationType; use Wallabag\CoreBundle\Form\Type\UserInformationType;
use Wallabag\CoreBundle\Tools\Utils; use Wallabag\CoreBundle\Tools\Utils;
@ -45,7 +47,7 @@ class ConfigController extends Controller
$activeTheme = $this->get('liip_theme.active_theme'); $activeTheme = $this->get('liip_theme.active_theme');
$activeTheme->setName($config->getTheme()); $activeTheme->setName($config->getTheme());
$this->get('session')->getFlashBag()->add( $this->addFlash(
'notice', 'notice',
'flashes.config.notice.config_saved' 'flashes.config.notice.config_saved'
); );
@ -67,7 +69,7 @@ class ConfigController extends Controller
$userManager->updateUser($user, true); $userManager->updateUser($user, true);
} }
$this->get('session')->getFlashBag()->add('notice', $message); $this->addFlash('notice', $message);
return $this->redirect($this->generateUrl('config') . '#set4'); return $this->redirect($this->generateUrl('config') . '#set4');
} }
@ -82,7 +84,7 @@ class ConfigController extends Controller
if ($userForm->isSubmitted() && $userForm->isValid()) { if ($userForm->isSubmitted() && $userForm->isValid()) {
$userManager->updateUser($user, true); $userManager->updateUser($user, true);
$this->get('session')->getFlashBag()->add( $this->addFlash(
'notice', 'notice',
'flashes.config.notice.user_updated' 'flashes.config.notice.user_updated'
); );
@ -90,17 +92,17 @@ class ConfigController extends Controller
return $this->redirect($this->generateUrl('config') . '#set3'); return $this->redirect($this->generateUrl('config') . '#set3');
} }
// handle rss information // handle feed information
$rssForm = $this->createForm(RssType::class, $config, ['action' => $this->generateUrl('config') . '#set2']); $feedForm = $this->createForm(FeedType::class, $config, ['action' => $this->generateUrl('config') . '#set2']);
$rssForm->handleRequest($request); $feedForm->handleRequest($request);
if ($rssForm->isSubmitted() && $rssForm->isValid()) { if ($feedForm->isSubmitted() && $feedForm->isValid()) {
$em->persist($config); $em->persist($config);
$em->flush(); $em->flush();
$this->get('session')->getFlashBag()->add( $this->addFlash(
'notice', 'notice',
'flashes.config.notice.rss_updated' 'flashes.config.notice.feed_updated'
); );
return $this->redirect($this->generateUrl('config') . '#set2'); return $this->redirect($this->generateUrl('config') . '#set2');
@ -130,7 +132,7 @@ class ConfigController extends Controller
$em->persist($taggingRule); $em->persist($taggingRule);
$em->flush(); $em->flush();
$this->get('session')->getFlashBag()->add( $this->addFlash(
'notice', 'notice',
'flashes.config.notice.tagging_rules_updated' 'flashes.config.notice.tagging_rules_updated'
); );
@ -141,22 +143,134 @@ class ConfigController extends Controller
return $this->render('WallabagCoreBundle:Config:index.html.twig', [ return $this->render('WallabagCoreBundle:Config:index.html.twig', [
'form' => [ 'form' => [
'config' => $configForm->createView(), 'config' => $configForm->createView(),
'rss' => $rssForm->createView(), 'feed' => $feedForm->createView(),
'pwd' => $pwdForm->createView(), 'pwd' => $pwdForm->createView(),
'user' => $userForm->createView(), 'user' => $userForm->createView(),
'new_tagging_rule' => $newTaggingRule->createView(), 'new_tagging_rule' => $newTaggingRule->createView(),
], ],
'rss' => [ 'feed' => [
'username' => $user->getUsername(), 'username' => $user->getUsername(),
'token' => $config->getRssToken(), 'token' => $config->getFeedToken(),
], ],
'twofactor_auth' => $this->getParameter('twofactor_auth'), 'twofactor_auth' => $this->getParameter('twofactor_auth'),
'wallabag_url' => $this->getParameter('domain_name'), 'wallabag_url' => $this->getParameter('domain_name'),
'enabled_users' => $this->get('wallabag_user.user_repository') 'enabled_users' => $this->get('wallabag_user.user_repository')->getSumEnabledUsers(),
->getSumEnabledUsers(),
]); ]);
} }
/**
* Enable 2FA using email.
*
* @Route("/config/otp/email", name="config_otp_email")
*/
public function otpEmailAction()
{
if (!$this->getParameter('twofactor_auth')) {
return $this->createNotFoundException('two_factor not enabled');
}
$user = $this->getUser();
$user->setGoogleAuthenticatorSecret(null);
$user->setBackupCodes(null);
$user->setEmailTwoFactor(true);
$this->container->get('fos_user.user_manager')->updateUser($user, true);
$this->addFlash(
'notice',
'flashes.config.notice.otp_enabled'
);
return $this->redirect($this->generateUrl('config') . '#set3');
}
/**
* Enable 2FA using OTP app, user will need to confirm the generated code from the app.
*
* @Route("/config/otp/app", name="config_otp_app")
*/
public function otpAppAction()
{
if (!$this->getParameter('twofactor_auth')) {
return $this->createNotFoundException('two_factor not enabled');
}
$user = $this->getUser();
$secret = $this->get('scheb_two_factor.security.google_authenticator')->generateSecret();
$user->setGoogleAuthenticatorSecret($secret);
$user->setEmailTwoFactor(false);
$backupCodes = (new BackupCodes())->toArray();
$backupCodesHashed = array_map(
function ($backupCode) {
return password_hash($backupCode, PASSWORD_DEFAULT);
},
$backupCodes
);
$user->setBackupCodes($backupCodesHashed);
$this->container->get('fos_user.user_manager')->updateUser($user, true);
return $this->render('WallabagCoreBundle:Config:otp_app.html.twig', [
'backupCodes' => $backupCodes,
'qr_code' => $this->get('scheb_two_factor.security.google_authenticator')->getQRContent($user),
]);
}
/**
* Cancelling 2FA using OTP app.
*
* @Route("/config/otp/app/cancel", name="config_otp_app_cancel")
*/
public function otpAppCancelAction()
{
if (!$this->getParameter('twofactor_auth')) {
return $this->createNotFoundException('two_factor not enabled');
}
$user = $this->getUser();
$user->setGoogleAuthenticatorSecret(null);
$user->setBackupCodes(null);
$this->container->get('fos_user.user_manager')->updateUser($user, true);
return $this->redirect($this->generateUrl('config') . '#set3');
}
/**
* Validate OTP code.
*
* @param Request $request
*
* @Route("/config/otp/app/check", name="config_otp_app_check")
*/
public function otpAppCheckAction(Request $request)
{
$isValid = $this->get('scheb_two_factor.security.google_authenticator')->checkCode(
$this->getUser(),
$request->get('_auth_code')
);
if (true === $isValid) {
$this->addFlash(
'notice',
'flashes.config.notice.otp_enabled'
);
return $this->redirect($this->generateUrl('config') . '#set3');
}
$this->addFlash(
'two_factor',
'scheb_two_factor.code_invalid'
);
return $this->redirect($this->generateUrl('config_otp_app'));
}
/** /**
* @param Request $request * @param Request $request
* *
@ -167,19 +281,19 @@ class ConfigController extends Controller
public function generateTokenAction(Request $request) public function generateTokenAction(Request $request)
{ {
$config = $this->getConfig(); $config = $this->getConfig();
$config->setRssToken(Utils::generateToken()); $config->setFeedToken(Utils::generateToken());
$em = $this->getDoctrine()->getManager(); $em = $this->getDoctrine()->getManager();
$em->persist($config); $em->persist($config);
$em->flush(); $em->flush();
if ($request->isXmlHttpRequest()) { if ($request->isXmlHttpRequest()) {
return new JsonResponse(['token' => $config->getRssToken()]); return new JsonResponse(['token' => $config->getFeedToken()]);
} }
$this->get('session')->getFlashBag()->add( $this->addFlash(
'notice', 'notice',
'flashes.config.notice.rss_token_updated' 'flashes.config.notice.feed_token_updated'
); );
return $this->redirect($this->generateUrl('config') . '#set2'); return $this->redirect($this->generateUrl('config') . '#set2');
@ -202,7 +316,7 @@ class ConfigController extends Controller
$em->remove($rule); $em->remove($rule);
$em->flush(); $em->flush();
$this->get('session')->getFlashBag()->add( $this->addFlash(
'notice', 'notice',
'flashes.config.notice.tagging_rules_deleted' 'flashes.config.notice.tagging_rules_deleted'
); );
@ -268,7 +382,7 @@ class ConfigController extends Controller
break; break;
} }
$this->get('session')->getFlashBag()->add( $this->addFlash(
'notice', 'notice',
'flashes.config.notice.' . $type . '_reset' 'flashes.config.notice.' . $type . '_reset'
); );
@ -329,6 +443,27 @@ class ConfigController extends Controller
return $this->redirect($request->headers->get('referer')); return $this->redirect($request->headers->get('referer'));
} }
/**
* Change the locale for the current user.
*
* @param Request $request
* @param string $language
*
* @Route("/locale/{language}", name="changeLocale")
*
* @return \Symfony\Component\HttpFoundation\RedirectResponse
*/
public function setLocaleAction(Request $request, $language = null)
{
$errors = $this->get('validator')->validate($language, (new LocaleConstraint()));
if (0 === \count($errors)) {
$request->getSession()->set('_locale', $language);
}
return $this->redirect($request->headers->get('referer', $this->generateUrl('homepage')));
}
/** /**
* Remove all tags for given tags and a given user and cleanup orphan tags. * Remove all tags for given tags and a given user and cleanup orphan tags.
* *

View file

@ -2,12 +2,13 @@
namespace Wallabag\CoreBundle\Controller; namespace Wallabag\CoreBundle\Controller;
use Doctrine\ORM\NoResultException;
use Pagerfanta\Adapter\DoctrineORMAdapter; use Pagerfanta\Adapter\DoctrineORMAdapter;
use Pagerfanta\Exception\OutOfRangeCurrentPageException; use Pagerfanta\Exception\OutOfRangeCurrentPageException;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Cache; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Cache;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Wallabag\CoreBundle\Entity\Entry; use Wallabag\CoreBundle\Entity\Entry;
use Wallabag\CoreBundle\Event\EntryDeletedEvent; use Wallabag\CoreBundle\Event\EntryDeletedEvent;
@ -232,6 +233,46 @@ class EntryController extends Controller
return $this->showEntries('starred', $request, $page); return $this->showEntries('starred', $request, $page);
} }
/**
* Shows untagged articles for current user.
*
* @param Request $request
* @param int $page
*
* @Route("/untagged/list/{page}", name="untagged", defaults={"page" = "1"})
*
* @return \Symfony\Component\HttpFoundation\Response
*/
public function showUntaggedEntriesAction(Request $request, $page)
{
return $this->showEntries('untagged', $request, $page);
}
/**
* Shows random entry depending on the given type.
*
* @param string $type
*
* @Route("/{type}/random", name="random_entry", requirements={"type": "unread|starred|archive|untagged|all"})
*
* @return \Symfony\Component\HttpFoundation\RedirectResponse
*/
public function redirectRandomEntryAction($type = 'all')
{
try {
$entry = $this->get('wallabag_core.entry_repository')
->getRandomEntry($this->getUser()->getId(), $type);
} catch (NoResultException $e) {
$bag = $this->get('session')->getFlashBag();
$bag->clear();
$bag->add('notice', 'flashes.entry.notice.no_random_entry');
return $this->redirect($this->generateUrl($type));
}
return $this->redirect($this->generateUrl('view', ['id' => $entry->getId()]));
}
/** /**
* Shows entry content. * Shows entry content.
* *
@ -465,54 +506,6 @@ class EntryController extends Controller
); );
} }
/**
* Shows untagged articles for current user.
*
* @param Request $request
* @param int $page
*
* @Route("/untagged/list/{page}", name="untagged", defaults={"page" = "1"})
*
* @return \Symfony\Component\HttpFoundation\Response
*/
public function showUntaggedEntriesAction(Request $request, $page)
{
return $this->showEntries('untagged', $request, $page);
}
/**
* Fetch content and update entry.
* In case it fails, $entry->getContent will return an error message.
*
* @param Entry $entry
* @param string $prefixMessage Should be the translation key: entry_saved or entry_reloaded
*/
private function updateEntry(Entry $entry, $prefixMessage = 'entry_saved')
{
$message = 'flashes.entry.notice.' . $prefixMessage;
try {
$this->get('wallabag_core.content_proxy')->updateEntry($entry, $entry->getUrl());
} catch (\Exception $e) {
$this->get('logger')->error('Error while saving an entry', [
'exception' => $e,
'entry' => $entry,
]);
$message = 'flashes.entry.notice.' . $prefixMessage . '_failed';
}
if (empty($entry->getDomainName())) {
$this->get('wallabag_core.content_proxy')->setEntryDomainName($entry);
}
if (empty($entry->getTitle())) {
$this->get('wallabag_core.content_proxy')->setDefaultEntryTitle($entry);
}
$this->get('session')->getFlashBag()->add('notice', $message);
}
/** /**
* Global method to retrieve entries depending on the given type * Global method to retrieve entries depending on the given type
* It returns the response to be send. * It returns the response to be send.
@ -532,11 +525,9 @@ class EntryController extends Controller
switch ($type) { switch ($type) {
case 'search': case 'search':
$qb = $repository->getBuilderForSearchByUser($this->getUser()->getId(), $searchTerm, $currentRoute); $qb = $repository->getBuilderForSearchByUser($this->getUser()->getId(), $searchTerm, $currentRoute);
break; break;
case 'untagged': case 'untagged':
$qb = $repository->getBuilderForUntaggedByUser($this->getUser()->getId()); $qb = $repository->getBuilderForUntaggedByUser($this->getUser()->getId());
break; break;
case 'starred': case 'starred':
$qb = $repository->getBuilderForStarredByUser($this->getUser()->getId()); $qb = $repository->getBuilderForStarredByUser($this->getUser()->getId());
@ -587,6 +578,39 @@ class EntryController extends Controller
); );
} }
/**
* Fetch content and update entry.
* In case it fails, $entry->getContent will return an error message.
*
* @param Entry $entry
* @param string $prefixMessage Should be the translation key: entry_saved or entry_reloaded
*/
private function updateEntry(Entry $entry, $prefixMessage = 'entry_saved')
{
$message = 'flashes.entry.notice.' . $prefixMessage;
try {
$this->get('wallabag_core.content_proxy')->updateEntry($entry, $entry->getUrl());
} catch (\Exception $e) {
$this->get('logger')->error('Error while saving an entry', [
'exception' => $e,
'entry' => $entry,
]);
$message = 'flashes.entry.notice.' . $prefixMessage . '_failed';
}
if (empty($entry->getDomainName())) {
$this->get('wallabag_core.content_proxy')->setEntryDomainName($entry);
}
if (empty($entry->getTitle())) {
$this->get('wallabag_core.content_proxy')->setDefaultEntryTitle($entry);
}
$this->get('session')->getFlashBag()->add('notice', $message);
}
/** /**
* Check if the logged user can manage the given entry. * Check if the logged user can manage the given entry.
* *

View file

@ -2,10 +2,10 @@
namespace Wallabag\CoreBundle\Controller; namespace Wallabag\CoreBundle\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Routing\Annotation\Route;
use Wallabag\CoreBundle\Entity\Entry; use Wallabag\CoreBundle\Entity\Entry;
/** /**

View file

@ -7,86 +7,97 @@ use Pagerfanta\Adapter\DoctrineORMAdapter;
use Pagerfanta\Exception\OutOfRangeCurrentPageException; use Pagerfanta\Exception\OutOfRangeCurrentPageException;
use Pagerfanta\Pagerfanta; use Pagerfanta\Pagerfanta;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter; use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Wallabag\CoreBundle\Entity\Tag; use Wallabag\CoreBundle\Entity\Tag;
use Wallabag\UserBundle\Entity\User; use Wallabag\UserBundle\Entity\User;
class RssController extends Controller class FeedController extends Controller
{ {
/** /**
* Shows unread entries for current user. * Shows unread entries for current user.
* *
* @Route("/{username}/{token}/unread.xml", name="unread_rss", defaults={"_format"="xml"}) * @Route("/feed/{username}/{token}/unread/{page}", name="unread_feed", defaults={"page"=1, "_format"="xml"})
* @ParamConverter("user", class="WallabagUserBundle:User", converter="username_rsstoken_converter") *
* @ParamConverter("user", class="WallabagUserBundle:User", converter="username_feed_token_converter")
*
* @param User $user
* @param $page
* *
* @return \Symfony\Component\HttpFoundation\Response * @return \Symfony\Component\HttpFoundation\Response
*/ */
public function showUnreadRSSAction(Request $request, User $user) public function showUnreadFeedAction(User $user, $page)
{ {
return $this->showEntries('unread', $user, $request->query->get('page', 1)); return $this->showEntries('unread', $user, $page);
} }
/** /**
* Shows read entries for current user. * Shows read entries for current user.
* *
* @Route("/{username}/{token}/archive.xml", name="archive_rss", defaults={"_format"="xml"}) * @Route("/feed/{username}/{token}/archive/{page}", name="archive_feed", defaults={"page"=1, "_format"="xml"})
* @ParamConverter("user", class="WallabagUserBundle:User", converter="username_rsstoken_converter") *
* @ParamConverter("user", class="WallabagUserBundle:User", converter="username_feed_token_converter")
*
* @param User $user
* @param $page
* *
* @return \Symfony\Component\HttpFoundation\Response * @return \Symfony\Component\HttpFoundation\Response
*/ */
public function showArchiveRSSAction(Request $request, User $user) public function showArchiveFeedAction(User $user, $page)
{ {
return $this->showEntries('archive', $user, $request->query->get('page', 1)); return $this->showEntries('archive', $user, $page);
} }
/** /**
* Shows starred entries for current user. * Shows starred entries for current user.
* *
* @Route("/{username}/{token}/starred.xml", name="starred_rss", defaults={"_format"="xml"}) * @Route("/feed/{username}/{token}/starred/{page}", name="starred_feed", defaults={"page"=1, "_format"="xml"})
* @ParamConverter("user", class="WallabagUserBundle:User", converter="username_rsstoken_converter") *
* @ParamConverter("user", class="WallabagUserBundle:User", converter="username_feed_token_converter")
*
* @param User $user
* @param $page
* *
* @return \Symfony\Component\HttpFoundation\Response * @return \Symfony\Component\HttpFoundation\Response
*/ */
public function showStarredRSSAction(Request $request, User $user) public function showStarredFeedAction(User $user, $page)
{ {
return $this->showEntries('starred', $user, $request->query->get('page', 1)); return $this->showEntries('starred', $user, $page);
} }
/** /**
* Shows all entries for current user. * Shows all entries for current user.
* *
* @Route("/{username}/{token}/all.xml", name="all_rss", defaults={"_format"="xml"}) * @Route("/feed/{username}/{token}/all/{page}", name="all_feed", defaults={"page"=1, "_format"="xml"})
* @ParamConverter("user", class="WallabagUserBundle:User", converter="username_rsstoken_converter") *
* @ParamConverter("user", class="WallabagUserBundle:User", converter="username_feed_token_converter")
* *
* @return \Symfony\Component\HttpFoundation\Response * @return \Symfony\Component\HttpFoundation\Response
*/ */
public function showAllRSSAction(Request $request, User $user) public function showAllFeedAction(User $user, $page)
{ {
return $this->showEntries('all', $user, $request->query->get('page', 1)); return $this->showEntries('all', $user, $page);
} }
/** /**
* Shows entries associated to a tag for current user. * Shows entries associated to a tag for current user.
* *
* @Route("/{username}/{token}/tags/{slug}.xml", name="tag_rss", defaults={"_format"="xml"}) * @Route("/feed/{username}/{token}/tags/{slug}/{page}", name="tag_feed", defaults={"page"=1, "_format"="xml"})
* @ParamConverter("user", class="WallabagUserBundle:User", converter="username_rsstoken_converter") *
* @ParamConverter("user", class="WallabagUserBundle:User", converter="username_feed_token_converter")
* @ParamConverter("tag", options={"mapping": {"slug": "slug"}}) * @ParamConverter("tag", options={"mapping": {"slug": "slug"}})
* *
* @return \Symfony\Component\HttpFoundation\Response * @return \Symfony\Component\HttpFoundation\Response
*/ */
public function showTagsAction(Request $request, User $user, Tag $tag) public function showTagsFeedAction(User $user, Tag $tag, $page)
{ {
$page = $request->query->get('page', 1);
$url = $this->generateUrl( $url = $this->generateUrl(
'tag_rss', 'tag_feed',
[ [
'username' => $user->getUsername(), 'username' => $user->getUsername(),
'token' => $user->getConfig()->getRssToken(), 'token' => $user->getConfig()->getFeedToken(),
'slug' => $tag->getSlug(), 'slug' => $tag->getSlug(),
], ],
UrlGeneratorInterface::ABSOLUTE_URL UrlGeneratorInterface::ABSOLUTE_URL
@ -119,12 +130,15 @@ class RssController extends Controller
return $this->render( return $this->render(
'@WallabagCore/themes/common/Entry/entries.xml.twig', '@WallabagCore/themes/common/Entry/entries.xml.twig',
[ [
'url_html' => $this->generateUrl('tag_entries', ['slug' => $tag->getSlug()], UrlGeneratorInterface::ABSOLUTE_URL), 'type' => 'tag',
'type' => 'tag (' . $tag->getLabel() . ')',
'url' => $url, 'url' => $url,
'entries' => $entries, 'entries' => $entries,
'user' => $user->getUsername(),
'domainName' => $this->getParameter('domain_name'),
'version' => $this->getParameter('wallabag_core.version'),
'tag' => $tag->getSlug(),
], ],
new Response('', 200, ['Content-Type' => 'application/rss+xml']) new Response('', 200, ['Content-Type' => 'application/atom+xml'])
); );
} }
@ -162,14 +176,14 @@ class RssController extends Controller
$pagerAdapter = new DoctrineORMAdapter($qb->getQuery(), true, false); $pagerAdapter = new DoctrineORMAdapter($qb->getQuery(), true, false);
$entries = new Pagerfanta($pagerAdapter); $entries = new Pagerfanta($pagerAdapter);
$perPage = $user->getConfig()->getRssLimit() ?: $this->getParameter('wallabag_core.rss_limit'); $perPage = $user->getConfig()->getFeedLimit() ?: $this->getParameter('wallabag_core.Feed_limit');
$entries->setMaxPerPage($perPage); $entries->setMaxPerPage($perPage);
$url = $this->generateUrl( $url = $this->generateUrl(
$type . '_rss', $type . '_feed',
[ [
'username' => $user->getUsername(), 'username' => $user->getUsername(),
'token' => $user->getConfig()->getRssToken(), 'token' => $user->getConfig()->getFeedToken(),
], ],
UrlGeneratorInterface::ABSOLUTE_URL UrlGeneratorInterface::ABSOLUTE_URL
); );
@ -178,19 +192,19 @@ class RssController extends Controller
$entries->setCurrentPage((int) $page); $entries->setCurrentPage((int) $page);
} catch (OutOfRangeCurrentPageException $e) { } catch (OutOfRangeCurrentPageException $e) {
if ($page > 1) { if ($page > 1) {
return $this->redirect($url . '?page=' . $entries->getNbPages(), 302); return $this->redirect($url . '/' . $entries->getNbPages());
} }
} }
return $this->render( return $this->render('@WallabagCore/themes/common/Entry/entries.xml.twig', [
'@WallabagCore/themes/common/Entry/entries.xml.twig', 'type' => $type,
[ 'url' => $url,
'url_html' => $this->generateUrl($type, [], UrlGeneratorInterface::ABSOLUTE_URL), 'entries' => $entries,
'type' => $type, 'user' => $user->getUsername(),
'url' => $url, 'domainName' => $this->getParameter('domain_name'),
'entries' => $entries, 'version' => $this->getParameter('wallabag_core.version'),
], ],
new Response('', 200, ['Content-Type' => 'application/rss+xml']) new Response('', 200, ['Content-Type' => 'application/atom+xml'])
); );
} }
} }

View file

@ -2,10 +2,9 @@
namespace Wallabag\CoreBundle\Controller; namespace Wallabag\CoreBundle\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
use Wallabag\CoreBundle\Entity\SiteCredential; use Wallabag\CoreBundle\Entity\SiteCredential;
use Wallabag\UserBundle\Entity\User; use Wallabag\UserBundle\Entity\User;
@ -19,8 +18,7 @@ class SiteCredentialController extends Controller
/** /**
* Lists all User entities. * Lists all User entities.
* *
* @Route("/", name="site_credentials_index") * @Route("/", name="site_credentials_index", methods={"GET"})
* @Method("GET")
*/ */
public function indexAction() public function indexAction()
{ {
@ -36,8 +34,7 @@ class SiteCredentialController extends Controller
/** /**
* Creates a new site credential entity. * Creates a new site credential entity.
* *
* @Route("/new", name="site_credentials_new") * @Route("/new", name="site_credentials_new", methods={"GET", "POST"})
* @Method({"GET", "POST"})
* *
* @param Request $request * @param Request $request
* *
@ -77,8 +74,7 @@ class SiteCredentialController extends Controller
/** /**
* Displays a form to edit an existing site credential entity. * Displays a form to edit an existing site credential entity.
* *
* @Route("/{id}/edit", name="site_credentials_edit") * @Route("/{id}/edit", name="site_credentials_edit", methods={"GET", "POST"})
* @Method({"GET", "POST"})
* *
* @param Request $request * @param Request $request
* @param SiteCredential $siteCredential * @param SiteCredential $siteCredential
@ -121,8 +117,7 @@ class SiteCredentialController extends Controller
/** /**
* Deletes a site credential entity. * Deletes a site credential entity.
* *
* @Route("/{id}", name="site_credentials_delete") * @Route("/{id}", name="site_credentials_delete", methods={"DELETE"})
* @Method("DELETE")
* *
* @param Request $request * @param Request $request
* @param SiteCredential $siteCredential * @param SiteCredential $siteCredential

View file

@ -2,8 +2,8 @@
namespace Wallabag\CoreBundle\Controller; namespace Wallabag\CoreBundle\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\Routing\Annotation\Route;
class StaticController extends Controller class StaticController extends Controller
{ {

View file

@ -5,12 +5,13 @@ namespace Wallabag\CoreBundle\Controller;
use Pagerfanta\Adapter\ArrayAdapter; use Pagerfanta\Adapter\ArrayAdapter;
use Pagerfanta\Exception\OutOfRangeCurrentPageException; use Pagerfanta\Exception\OutOfRangeCurrentPageException;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter; use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
use Wallabag\CoreBundle\Entity\Entry; use Wallabag\CoreBundle\Entity\Entry;
use Wallabag\CoreBundle\Entity\Tag; use Wallabag\CoreBundle\Entity\Tag;
use Wallabag\CoreBundle\Form\Type\NewTagType; use Wallabag\CoreBundle\Form\Type\NewTagType;
use Wallabag\CoreBundle\Form\Type\RenameTagType;
class TagController extends Controller class TagController extends Controller
{ {
@ -87,8 +88,14 @@ class TagController extends Controller
$tags = $this->get('wallabag_core.tag_repository') $tags = $this->get('wallabag_core.tag_repository')
->findAllFlatTagsWithNbEntries($this->getUser()->getId()); ->findAllFlatTagsWithNbEntries($this->getUser()->getId());
$renameForms = [];
foreach ($tags as $tag) {
$renameForms[$tag['id']] = $this->createForm(RenameTagType::class, new Tag())->createView();
}
return $this->render('WallabagCoreBundle:Tag:tags.html.twig', [ return $this->render('WallabagCoreBundle:Tag:tags.html.twig', [
'tags' => $tags, 'tags' => $tags,
'renameForms' => $renameForms,
]); ]);
} }
@ -130,4 +137,48 @@ class TagController extends Controller
'tag' => $tag, 'tag' => $tag,
]); ]);
} }
/**
* Rename a given tag with a new label
* Create a new tag with the new name and drop the old one.
*
* @param Tag $tag
* @param Request $request
*
* @Route("/tag/rename/{slug}", name="tag_rename")
* @ParamConverter("tag", options={"mapping": {"slug": "slug"}})
*
* @return \Symfony\Component\HttpFoundation\Response
*/
public function renameTagAction(Tag $tag, Request $request)
{
$form = $this->createForm(RenameTagType::class, new Tag());
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$entries = $this->get('wallabag_core.entry_repository')->findAllByTagId(
$this->getUser()->getId(),
$tag->getId()
);
foreach ($entries as $entry) {
$this->get('wallabag_core.tags_assigner')->assignTagsToEntry(
$entry,
$form->get('label')->getData()
);
$entry->removeTag($tag);
}
$em = $this->getDoctrine()->getManager();
$em->flush();
}
$this->get('session')->getFlashBag()->add(
'notice',
'flashes.tag.notice.tag_renamed'
);
$redirectUrl = $this->get('wallabag_core.helper.redirect')->to($request->headers->get('referer'), '', true);
return $this->redirect($redirectUrl);
}
} }

View file

@ -1,13 +1,14 @@
<?php <?php
namespace Wallabag\CoreBundle\DataFixtures\ORM; namespace Wallabag\CoreBundle\DataFixtures;
use Doctrine\Common\DataFixtures\AbstractFixture; use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Common\DataFixtures\OrderedFixtureInterface; use Doctrine\Common\DataFixtures\DependentFixtureInterface;
use Doctrine\Common\Persistence\ObjectManager; use Doctrine\Common\Persistence\ObjectManager;
use Wallabag\CoreBundle\Entity\Config; use Wallabag\CoreBundle\Entity\Config;
use Wallabag\UserBundle\DataFixtures\UserFixtures;
class LoadConfigData extends AbstractFixture implements OrderedFixtureInterface class ConfigFixtures extends Fixture implements DependentFixtureInterface
{ {
/** /**
* {@inheritdoc} * {@inheritdoc}
@ -60,8 +61,10 @@ class LoadConfigData extends AbstractFixture implements OrderedFixtureInterface
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
public function getOrder() public function getDependencies()
{ {
return 20; return [
UserFixtures::class,
];
} }
} }

View file

@ -1,13 +1,14 @@
<?php <?php
namespace Wallabag\CoreBundle\DataFixtures\ORM; namespace Wallabag\CoreBundle\DataFixtures;
use Doctrine\Common\DataFixtures\AbstractFixture; use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Common\DataFixtures\OrderedFixtureInterface; use Doctrine\Common\DataFixtures\DependentFixtureInterface;
use Doctrine\Common\Persistence\ObjectManager; use Doctrine\Common\Persistence\ObjectManager;
use Wallabag\CoreBundle\Entity\Entry; use Wallabag\CoreBundle\Entity\Entry;
use Wallabag\UserBundle\DataFixtures\UserFixtures;
class LoadEntryData extends AbstractFixture implements OrderedFixtureInterface class EntryFixtures extends Fixture implements DependentFixtureInterface
{ {
/** /**
* {@inheritdoc} * {@inheritdoc}
@ -127,8 +128,11 @@ class LoadEntryData extends AbstractFixture implements OrderedFixtureInterface
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
public function getOrder() public function getDependencies()
{ {
return 30; return [
UserFixtures::class,
TagFixtures::class,
];
} }
} }

View file

@ -1,15 +1,14 @@
<?php <?php
namespace Wallabag\CoreBundle\DataFixtures\ORM; namespace Wallabag\CoreBundle\DataFixtures;
use Craue\ConfigBundle\Entity\Setting; use Craue\ConfigBundle\Entity\Setting;
use Doctrine\Common\DataFixtures\AbstractFixture; use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
use Doctrine\Common\Persistence\ObjectManager; use Doctrine\Common\Persistence\ObjectManager;
use Symfony\Component\DependencyInjection\ContainerAwareInterface; use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\ContainerInterface;
class LoadSettingData extends AbstractFixture implements OrderedFixtureInterface, ContainerAwareInterface class SettingFixtures extends Fixture implements ContainerAwareInterface
{ {
/** /**
* @var ContainerInterface * @var ContainerInterface
@ -36,12 +35,4 @@ class LoadSettingData extends AbstractFixture implements OrderedFixtureInterface
$manager->flush(); $manager->flush();
} }
/**
* {@inheritdoc}
*/
public function getOrder()
{
return 29;
}
} }

View file

@ -1,15 +1,16 @@
<?php <?php
namespace Wallabag\CoreBundle\DataFixtures\ORM; namespace Wallabag\CoreBundle\DataFixtures;
use Doctrine\Common\DataFixtures\AbstractFixture; use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Common\DataFixtures\OrderedFixtureInterface; use Doctrine\Common\DataFixtures\DependentFixtureInterface;
use Doctrine\Common\Persistence\ObjectManager; use Doctrine\Common\Persistence\ObjectManager;
use Symfony\Component\DependencyInjection\ContainerAwareInterface; use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\ContainerInterface;
use Wallabag\CoreBundle\Entity\SiteCredential; use Wallabag\CoreBundle\Entity\SiteCredential;
use Wallabag\UserBundle\DataFixtures\UserFixtures;
class LoadSiteCredentialData extends AbstractFixture implements OrderedFixtureInterface, ContainerAwareInterface class SiteCredentialFixtures extends Fixture implements DependentFixtureInterface, ContainerAwareInterface
{ {
/** /**
* @var ContainerInterface * @var ContainerInterface
@ -46,8 +47,10 @@ class LoadSiteCredentialData extends AbstractFixture implements OrderedFixtureIn
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
public function getOrder() public function getDependencies()
{ {
return 50; return [
UserFixtures::class,
];
} }
} }

View file

@ -1,13 +1,12 @@
<?php <?php
namespace Wallabag\CoreBundle\DataFixtures\ORM; namespace Wallabag\CoreBundle\DataFixtures;
use Doctrine\Common\DataFixtures\AbstractFixture; use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
use Doctrine\Common\Persistence\ObjectManager; use Doctrine\Common\Persistence\ObjectManager;
use Wallabag\CoreBundle\Entity\Tag; use Wallabag\CoreBundle\Entity\Tag;
class LoadTagData extends AbstractFixture implements OrderedFixtureInterface class TagFixtures extends Fixture
{ {
/** /**
* {@inheritdoc} * {@inheritdoc}
@ -33,12 +32,4 @@ class LoadTagData extends AbstractFixture implements OrderedFixtureInterface
$manager->flush(); $manager->flush();
} }
/**
* {@inheritdoc}
*/
public function getOrder()
{
return 25;
}
} }

View file

@ -1,13 +1,13 @@
<?php <?php
namespace Wallabag\CoreBundle\DataFixtures\ORM; namespace Wallabag\CoreBundle\DataFixtures;
use Doctrine\Common\DataFixtures\AbstractFixture; use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Common\DataFixtures\OrderedFixtureInterface; use Doctrine\Common\DataFixtures\DependentFixtureInterface;
use Doctrine\Common\Persistence\ObjectManager; use Doctrine\Common\Persistence\ObjectManager;
use Wallabag\CoreBundle\Entity\TaggingRule; use Wallabag\CoreBundle\Entity\TaggingRule;
class LoadTaggingRuleData extends AbstractFixture implements OrderedFixtureInterface class TaggingRuleFixtures extends Fixture implements DependentFixtureInterface
{ {
/** /**
* {@inheritdoc} * {@inheritdoc}
@ -49,8 +49,10 @@ class LoadTaggingRuleData extends AbstractFixture implements OrderedFixtureInter
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
public function getOrder() public function getDependencies()
{ {
return 40; return [
ConfigFixtures::class,
];
} }
} }

View file

@ -18,7 +18,7 @@ class WallabagCoreExtension extends Extension
$container->setParameter('wallabag_core.items_on_page', $config['items_on_page']); $container->setParameter('wallabag_core.items_on_page', $config['items_on_page']);
$container->setParameter('wallabag_core.theme', $config['theme']); $container->setParameter('wallabag_core.theme', $config['theme']);
$container->setParameter('wallabag_core.language', $config['language']); $container->setParameter('wallabag_core.language', $config['language']);
$container->setParameter('wallabag_core.rss_limit', $config['rss_limit']); $container->setParameter('wallabag_core.feed_limit', $config['rss_limit']);
$container->setParameter('wallabag_core.reading_speed', $config['reading_speed']); $container->setParameter('wallabag_core.reading_speed', $config['reading_speed']);
$container->setParameter('wallabag_core.version', $config['version']); $container->setParameter('wallabag_core.version', $config['version']);
$container->setParameter('wallabag_core.paypal_url', $config['paypal_url']); $container->setParameter('wallabag_core.paypal_url', $config['paypal_url']);

View file

@ -1,25 +0,0 @@
<?php
namespace Wallabag\CoreBundle\Doctrine\DBAL\Driver;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Driver\PDOPgSql\Driver;
use Wallabag\CoreBundle\Doctrine\DBAL\Schema\CustomPostgreSqlSchemaManager;
/**
* This custom driver allow to use a different schema manager
* So we can fix the PostgreSQL 10 problem.
*
* @see https://github.com/wallabag/wallabag/issues/3479
* @see https://github.com/doctrine/dbal/issues/2868
*/
class CustomPostgreSQLDriver extends Driver
{
/**
* {@inheritdoc}
*/
public function getSchemaManager(Connection $conn)
{
return new CustomPostgreSqlSchemaManager($conn);
}
}

View file

@ -1,38 +0,0 @@
<?php
namespace Wallabag\CoreBundle\Doctrine\DBAL\Schema;
use Doctrine\DBAL\Schema\PostgreSqlSchemaManager;
use Doctrine\DBAL\Schema\Sequence;
/**
* This custom schema manager fix the PostgreSQL 10 problem.
*
* @see https://github.com/wallabag/wallabag/issues/3479
* @see https://github.com/doctrine/dbal/issues/2868
*/
class CustomPostgreSqlSchemaManager extends PostgreSqlSchemaManager
{
/**
* {@inheritdoc}
*/
protected function _getPortableSequenceDefinition($sequence)
{
$sequenceName = $sequence['relname'];
if ('public' !== $sequence['schemaname']) {
$sequenceName = $sequence['schemaname'] . '.' . $sequence['relname'];
}
$query = 'SELECT min_value, increment_by FROM ' . $this->_platform->quoteIdentifier($sequenceName);
// the `method_exists` is only to avoid test to fail:
// DAMA\DoctrineTestBundle\Doctrine\DBAL\StaticConnection doesn't support the `getServerVersion`
if (method_exists($this->_conn->getWrappedConnection(), 'getServerVersion') && (float) ($this->_conn->getWrappedConnection()->getServerVersion()) >= 10) {
$query = "SELECT min_value, increment_by FROM pg_sequences WHERE schemaname = 'public' AND sequencename = " . $this->_conn->quote($sequenceName);
}
$data = $this->_conn->fetchAll($query);
return new Sequence($sequenceName, $data[0]['increment_by'], $data[0]['min_value']);
}
}

View file

@ -2,8 +2,8 @@
namespace Wallabag\CoreBundle\Doctrine; namespace Wallabag\CoreBundle\Doctrine;
use Doctrine\DBAL\Migrations\AbstractMigration;
use Doctrine\DBAL\Schema\Schema; use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
use Symfony\Component\DependencyInjection\ContainerAwareInterface; use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\ContainerInterface;

View file

@ -60,21 +60,21 @@ class Config
/** /**
* @var string * @var string
* *
* @ORM\Column(name="rss_token", type="string", nullable=true) * @ORM\Column(name="feed_token", type="string", nullable=true)
*/ */
private $rssToken; private $feedToken;
/** /**
* @var int * @var int
* *
* @ORM\Column(name="rss_limit", type="integer", nullable=true) * @ORM\Column(name="feed_limit", type="integer", nullable=true)
* @Assert\Range( * @Assert\Range(
* min = 1, * min = 1,
* max = 100000, * max = 100000,
* maxMessage = "validator.rss_limit_too_high" * maxMessage = "validator.feed_limit_too_high"
* ) * )
*/ */
private $rssLimit; private $feedLimit;
/** /**
* @var float * @var float
@ -231,51 +231,51 @@ class Config
} }
/** /**
* Set rssToken. * Set feed Token.
* *
* @param string $rssToken * @param string $feedToken
* *
* @return Config * @return Config
*/ */
public function setRssToken($rssToken) public function setFeedToken($feedToken)
{ {
$this->rssToken = $rssToken; $this->feedToken = $feedToken;
return $this; return $this;
} }
/** /**
* Get rssToken. * Get feedToken.
* *
* @return string * @return string
*/ */
public function getRssToken() public function getFeedToken()
{ {
return $this->rssToken; return $this->feedToken;
} }
/** /**
* Set rssLimit. * Set Feed Limit.
* *
* @param int $rssLimit * @param int $feedLimit
* *
* @return Config * @return Config
*/ */
public function setRssLimit($rssLimit) public function setFeedLimit($feedLimit)
{ {
$this->rssLimit = $rssLimit; $this->feedLimit = $feedLimit;
return $this; return $this;
} }
/** /**
* Get rssLimit. * Get Feed Limit.
* *
* @return int * @return int
*/ */
public function getRssLimit() public function getFeedLimit()
{ {
return $this->rssLimit; return $this->feedLimit;
} }
/** /**

View file

@ -13,6 +13,7 @@ use JMS\Serializer\Annotation\XmlRoot;
use Symfony\Component\Validator\Constraints as Assert; use Symfony\Component\Validator\Constraints as Assert;
use Wallabag\AnnotationBundle\Entity\Annotation; use Wallabag\AnnotationBundle\Entity\Annotation;
use Wallabag\CoreBundle\Helper\EntityTimestampsTrait; use Wallabag\CoreBundle\Helper\EntityTimestampsTrait;
use Wallabag\CoreBundle\Helper\UrlHasher;
use Wallabag\UserBundle\Entity\User; use Wallabag\UserBundle\Entity\User;
/** /**
@ -25,7 +26,8 @@ use Wallabag\UserBundle\Entity\User;
* options={"collate"="utf8mb4_unicode_ci", "charset"="utf8mb4"}, * options={"collate"="utf8mb4_unicode_ci", "charset"="utf8mb4"},
* indexes={ * indexes={
* @ORM\Index(name="created_at", columns={"created_at"}), * @ORM\Index(name="created_at", columns={"created_at"}),
* @ORM\Index(name="uid", columns={"uid"}) * @ORM\Index(name="uid", columns={"uid"}),
* @ORM\Index(name="hashed_url_user_id", columns={"user_id", "hashed_url"}, options={"lengths"={null, 40}})
* } * }
* ) * )
* @ORM\HasLifecycleCallbacks() * @ORM\HasLifecycleCallbacks()
@ -75,6 +77,13 @@ class Entry
*/ */
private $url; private $url;
/**
* @var string
*
* @ORM\Column(name="hashed_url", type="string", length=40, nullable=true)
*/
private $hashedUrl;
/** /**
* @var bool * @var bool
* *
@ -86,6 +95,15 @@ class Entry
*/ */
private $isArchived = false; private $isArchived = false;
/**
* @var \DateTime
*
* @ORM\Column(name="archived_at", type="datetime", nullable=true)
*
* @Groups({"entries_for_user", "export_all"})
*/
private $archivedAt = null;
/** /**
* @var bool * @var bool
* *
@ -307,6 +325,7 @@ class Entry
public function setUrl($url) public function setUrl($url)
{ {
$this->url = $url; $this->url = $url;
$this->hashedUrl = UrlHasher::hashUrl($url);
return $this; return $this;
} }
@ -335,6 +354,44 @@ class Entry
return $this; return $this;
} }
/**
* update isArchived and archive_at fields.
*
* @param bool $isArchived
*
* @return Entry
*/
public function updateArchived($isArchived = false)
{
$this->setArchived($isArchived);
$this->setArchivedAt(null);
if ($this->isArchived()) {
$this->setArchivedAt(new \DateTime());
}
return $this;
}
/**
* @return \DateTime|null
*/
public function getArchivedAt()
{
return $this->archivedAt;
}
/**
* @param \DateTime|null $archivedAt
*
* @return Entry
*/
public function setArchivedAt($archivedAt = null)
{
$this->archivedAt = $archivedAt;
return $this;
}
/** /**
* Get isArchived. * Get isArchived.
* *
@ -357,7 +414,7 @@ class Entry
public function toggleArchive() public function toggleArchive()
{ {
$this->isArchived = $this->isArchived() ^ 1; $this->updateArchived($this->isArchived() ^ 1);
return $this; return $this;
} }
@ -864,4 +921,24 @@ class Entry
{ {
return $this->originUrl; return $this->originUrl;
} }
/**
* @return string
*/
public function getHashedUrl()
{
return $this->hashedUrl;
}
/**
* @param mixed $hashedUrl
*
* @return Entry
*/
public function setHashedUrl($hashedUrl)
{
$this->hashedUrl = $hashedUrl;
return $this;
}
} }

View file

@ -59,6 +59,13 @@ class SiteCredential
*/ */
private $createdAt; private $createdAt;
/**
* @var \DateTime
*
* @ORM\Column(name="updated_at", type="datetime")
*/
private $updatedAt;
/** /**
* @ORM\ManyToOne(targetEntity="Wallabag\UserBundle\Entity\User", inversedBy="siteCredentials") * @ORM\ManyToOne(targetEntity="Wallabag\UserBundle\Entity\User", inversedBy="siteCredentials")
*/ */
@ -178,6 +185,16 @@ class SiteCredential
return $this->createdAt; return $this->createdAt;
} }
/**
* Get updatedAt.
*
* @return \DateTime
*/
public function getUpdatedAt()
{
return $this->updatedAt;
}
/** /**
* @return User * @return User
*/ */

View file

@ -3,7 +3,7 @@
namespace Wallabag\CoreBundle\Entity; namespace Wallabag\CoreBundle\Entity;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use KPhoen\RulerZBundle\Validator\Constraints as RulerZAssert; use Symfony\Bridge\RulerZ\Validator\Constraints as RulerZAssert;
use Symfony\Component\Validator\Constraints as Assert; use Symfony\Component\Validator\Constraints as Assert;
/** /**

View file

@ -6,8 +6,10 @@ use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent; use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
/** /**
* Stores the locale of the user in the session after the * Stores the locale of the user in the session after the login.
* login. This can be used by the LocaleListener afterwards. * If no locale are defined (if user doesn't change it from the login screen), override it with the user's config one.
*
* This can be used by the LocaleListener afterwards.
* *
* @see http://symfony.com/doc/master/cookbook/session/locale_sticky_session.html * @see http://symfony.com/doc/master/cookbook/session/locale_sticky_session.html
*/ */
@ -30,7 +32,7 @@ class UserLocaleListener
{ {
$user = $event->getAuthenticationToken()->getUser(); $user = $event->getAuthenticationToken()->getUser();
if (null !== $user->getConfig()->getLanguage()) { if (null !== $user->getConfig()->getLanguage() && null === $this->session->get('_locale')) {
$this->session->set('_locale', $user->getConfig()->getLanguage()); $this->session->set('_locale', $user->getConfig()->getLanguage());
} }
} }

View file

@ -22,11 +22,13 @@ class EditEntryType extends AbstractType
'disabled' => true, 'disabled' => true,
'required' => false, 'required' => false,
'label' => 'entry.edit.url_label', 'label' => 'entry.edit.url_label',
'default_protocol' => null,
]) ])
->add('origin_url', UrlType::class, [ ->add('origin_url', UrlType::class, [
'required' => false, 'required' => false,
'property_path' => 'originUrl', 'property_path' => 'originUrl',
'label' => 'entry.edit.origin_url_label', 'label' => 'entry.edit.origin_url_label',
'default_protocol' => null,
]) ])
->add('save', SubmitType::class, [ ->add('save', SubmitType::class, [
'label' => 'entry.edit.save_label', 'label' => 'entry.edit.save_label',

View file

@ -7,14 +7,14 @@ use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\OptionsResolver\OptionsResolver;
class RssType extends AbstractType class FeedType extends AbstractType
{ {
public function buildForm(FormBuilderInterface $builder, array $options) public function buildForm(FormBuilderInterface $builder, array $options)
{ {
$builder $builder
->add('rss_limit', null, [ ->add('feed_limit', null, [
'label' => 'config.form_rss.rss_limit', 'label' => 'config.form_feed.feed_limit',
'property_path' => 'rssLimit', 'property_path' => 'feedLimit',
]) ])
->add('save', SubmitType::class, [ ->add('save', SubmitType::class, [
'label' => 'config.form.save', 'label' => 'config.form.save',
@ -31,6 +31,6 @@ class RssType extends AbstractType
public function getBlockPrefix() public function getBlockPrefix()
{ {
return 'rss_config'; return 'feed_config';
} }
} }

View file

@ -15,6 +15,7 @@ class NewEntryType extends AbstractType
->add('url', UrlType::class, [ ->add('url', UrlType::class, [
'required' => true, 'required' => true,
'label' => 'entry.new.form_new.url_label', 'label' => 'entry.new.form_new.url_label',
'default_protocol' => null,
]) ])
; ;
} }

View file

@ -0,0 +1,35 @@
<?php
namespace Wallabag\CoreBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class RenameTagType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('label', TextType::class, [
'required' => true,
'attr' => [
'placeholder' => 'tag.rename.placeholder',
],
])
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => 'Wallabag\CoreBundle\Entity\Tag',
]);
}
public function getBlockPrefix()
{
return 'tag';
}
}

View file

@ -21,9 +21,14 @@ class UserInformationType extends AbstractType
->add('email', EmailType::class, [ ->add('email', EmailType::class, [
'label' => 'config.form_user.email_label', 'label' => 'config.form_user.email_label',
]) ])
->add('twoFactorAuthentication', CheckboxType::class, [ ->add('emailTwoFactor', CheckboxType::class, [
'required' => false, 'required' => false,
'label' => 'config.form_user.twoFactorAuthentication_label', 'label' => 'config.form_user.emailTwoFactor_label',
])
->add('googleTwoFactor', CheckboxType::class, [
'required' => false,
'label' => 'config.form_user.googleTwoFactor_label',
'mapped' => false,
]) ])
->add('save', SubmitType::class, [ ->add('save', SubmitType::class, [
'label' => 'config.form.save', 'label' => 'config.form.save',

View file

@ -12,8 +12,8 @@ use Wallabag\CoreBundle\Entity\Entry;
use Wallabag\CoreBundle\Tools\Utils; use Wallabag\CoreBundle\Tools\Utils;
/** /**
* This kind of proxy class take care of getting the content from an url * This kind of proxy class takes care of getting the content from an url
* and update the entry with what it found. * and updates the entry with what it found.
*/ */
class ContentProxy class ContentProxy
{ {
@ -54,7 +54,11 @@ class ContentProxy
if ((empty($content) || false === $this->validateContent($content)) && false === $disableContentUpdate) { if ((empty($content) || false === $this->validateContent($content)) && false === $disableContentUpdate) {
$fetchedContent = $this->graby->fetchContent($url); $fetchedContent = $this->graby->fetchContent($url);
$fetchedContent['title'] = $this->sanitizeContentTitle($fetchedContent['title'], $fetchedContent['content_type']);
$fetchedContent['title'] = $this->sanitizeContentTitle(
$fetchedContent['title'],
isset($fetchedContent['headers']['content-type']) ? $fetchedContent['headers']['content-type'] : ''
);
// when content is imported, we have information in $content // when content is imported, we have information in $content
// in case fetching content goes bad, we'll keep the imported information instead of overriding them // in case fetching content goes bad, we'll keep the imported information instead of overriding them
@ -188,8 +192,8 @@ class ContentProxy
/** /**
* Try to sanitize the title of the fetched content from wrong character encodings and invalid UTF-8 character. * Try to sanitize the title of the fetched content from wrong character encodings and invalid UTF-8 character.
* *
* @param $title * @param string $title
* @param $contentType * @param string $contentType
* *
* @return string * @return string
*/ */
@ -253,16 +257,14 @@ class ContentProxy
if (!empty($content['title'])) { if (!empty($content['title'])) {
$entry->setTitle($content['title']); $entry->setTitle($content['title']);
} elseif (!empty($content['open_graph']['og_title'])) {
$entry->setTitle($content['open_graph']['og_title']);
} }
if (empty($content['html'])) { if (empty($content['html'])) {
$content['html'] = $this->fetchingErrorMessage; $content['html'] = $this->fetchingErrorMessage;
if (!empty($content['open_graph']['og_description'])) { if (!empty($content['description'])) {
$content['html'] .= '<p><i>But we found a short description: </i></p>'; $content['html'] .= '<p><i>But we found a short description: </i></p>';
$content['html'] .= $content['open_graph']['og_description']; $content['html'] .= $content['description'];
} }
} }
@ -277,8 +279,8 @@ class ContentProxy
$entry->setPublishedBy($content['authors']); $entry->setPublishedBy($content['authors']);
} }
if (!empty($content['all_headers']) && $this->storeArticleHeaders) { if (!empty($content['headers'])) {
$entry->setHeaders($content['all_headers']); $entry->setHeaders($content['headers']);
} }
if (!empty($content['date'])) { if (!empty($content['date'])) {
@ -289,17 +291,30 @@ class ContentProxy
$this->updateLanguage($entry, $content['language']); $this->updateLanguage($entry, $content['language']);
} }
if (!empty($content['open_graph']['og_image'])) { $previewPictureUrl = '';
$this->updatePreviewPicture($entry, $content['open_graph']['og_image']); if (!empty($content['image'])) {
$previewPictureUrl = $content['image'];
} }
// if content is an image, define it as a preview too // if content is an image, define it as a preview too
if (!empty($content['content_type']) && \in_array($this->mimeGuesser->guess($content['content_type']), ['jpeg', 'jpg', 'gif', 'png'], true)) { if (!empty($content['headers']['content-type']) && \in_array($this->mimeGuesser->guess($content['headers']['content-type']), ['jpeg', 'jpg', 'gif', 'png'], true)) {
$this->updatePreviewPicture($entry, $content['url']); $previewPictureUrl = $content['url'];
} elseif (empty($previewPictureUrl)) {
$this->logger->debug('Extracting images from content to provide a default preview picture');
$imagesUrls = DownloadImages::extractImagesUrlsFromHtml($content['html']);
$this->logger->debug(\count($imagesUrls) . ' pictures found');
if (!empty($imagesUrls)) {
$previewPictureUrl = $imagesUrls[0];
}
} }
if (!empty($content['content_type'])) { if (!empty($content['headers']['content-type'])) {
$entry->setMimetype($content['content_type']); $entry->setMimetype($content['headers']['content-type']);
}
if (!empty($previewPictureUrl)) {
$this->updatePreviewPicture($entry, $previewPictureUrl);
} }
try { try {

View file

@ -2,8 +2,13 @@
namespace Wallabag\CoreBundle\Helper; namespace Wallabag\CoreBundle\Helper;
use GuzzleHttp\Client; use Http\Client\Common\HttpMethodsClient;
use GuzzleHttp\Message\Response; use Http\Client\Common\Plugin\ErrorPlugin;
use Http\Client\Common\PluginClient;
use Http\Client\HttpClient;
use Http\Discovery\MessageFactoryDiscovery;
use Http\Message\MessageFactory;
use Psr\Http\Message\ResponseInterface;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use Symfony\Component\DomCrawler\Crawler; use Symfony\Component\DomCrawler\Crawler;
use Symfony\Component\Finder\Finder; use Symfony\Component\Finder\Finder;
@ -19,9 +24,9 @@ class DownloadImages
private $mimeGuesser; private $mimeGuesser;
private $wallabagUrl; private $wallabagUrl;
public function __construct(Client $client, $baseFolder, $wallabagUrl, LoggerInterface $logger) public function __construct(HttpClient $client, $baseFolder, $wallabagUrl, LoggerInterface $logger, MessageFactory $messageFactory = null)
{ {
$this->client = $client; $this->client = new HttpMethodsClient(new PluginClient($client, [new ErrorPlugin()]), $messageFactory ?: MessageFactoryDiscovery::find());
$this->baseFolder = $baseFolder; $this->baseFolder = $baseFolder;
$this->wallabagUrl = rtrim($wallabagUrl, '/'); $this->wallabagUrl = rtrim($wallabagUrl, '/');
$this->logger = $logger; $this->logger = $logger;
@ -30,6 +35,25 @@ class DownloadImages
$this->setFolder(); $this->setFolder();
} }
/**
* Process the html and extract images URLs from it.
*
* @param string $html
*
* @return string[]
*/
public static function extractImagesUrlsFromHtml($html)
{
$crawler = new Crawler($html);
$imagesCrawler = $crawler
->filterXpath('//img');
$imagesUrls = $imagesCrawler
->extract(['src']);
$imagesSrcsetUrls = self::getSrcsetUrls($imagesCrawler);
return array_unique(array_merge($imagesUrls, $imagesSrcsetUrls));
}
/** /**
* Process the html and extract image from it, save them to local and return the updated html. * Process the html and extract image from it, save them to local and return the updated html.
* *
@ -41,13 +65,7 @@ class DownloadImages
*/ */
public function processHtml($entryId, $html, $url) public function processHtml($entryId, $html, $url)
{ {
$crawler = new Crawler($html); $imagesUrls = self::extractImagesUrlsFromHtml($html);
$imagesCrawler = $crawler
->filterXpath('//img');
$imagesUrls = $imagesCrawler
->extract(['src']);
$imagesSrcsetUrls = $this->getSrcsetUrls($imagesCrawler);
$imagesUrls = array_unique(array_merge($imagesUrls, $imagesSrcsetUrls));
$relativePath = $this->getRelativePath($entryId); $relativePath = $this->getRelativePath($entryId);
@ -122,7 +140,7 @@ class DownloadImages
$localPath = $folderPath . '/' . $hashImage . '.' . $ext; $localPath = $folderPath . '/' . $hashImage . '.' . $ext;
try { try {
$im = imagecreatefromstring($res->getBody()); $im = imagecreatefromstring((string) $res->getBody());
} catch (\Exception $e) { } catch (\Exception $e) {
$im = false; $im = false;
} }
@ -135,7 +153,21 @@ class DownloadImages
switch ($ext) { switch ($ext) {
case 'gif': case 'gif':
imagegif($im, $localPath); // use Imagick if available to keep GIF animation
if (class_exists('\\Imagick')) {
try {
$imagick = new \Imagick();
$imagick->readImageBlob($res->getBody());
$imagick->setImageFormat('gif');
$imagick->writeImages($localPath, true);
} catch (\Exception $e) {
// if Imagick fail, fallback to the default solution
imagegif($im, $localPath);
}
} else {
imagegif($im, $localPath);
}
$this->logger->debug('DownloadImages: Re-creating gif'); $this->logger->debug('DownloadImages: Re-creating gif');
break; break;
case 'jpeg': case 'jpeg':
@ -185,7 +217,7 @@ class DownloadImages
* *
* @return array An array of urls * @return array An array of urls
*/ */
private function getSrcsetUrls(Crawler $imagesCrawler) private static function getSrcsetUrls(Crawler $imagesCrawler)
{ {
$urls = []; $urls = [];
$iterator = $imagesCrawler $iterator = $imagesCrawler
@ -279,14 +311,14 @@ class DownloadImages
/** /**
* Retrieve and validate the extension from the response of the url of the image. * Retrieve and validate the extension from the response of the url of the image.
* *
* @param Response $res Guzzle Response * @param ResponseInterface $res Http Response
* @param string $imagePath Path from the src image from the content (used for log only) * @param string $imagePath Path from the src image from the content (used for log only)
* *
* @return string|false Extension name or false if validation failed * @return string|false Extension name or false if validation failed
*/ */
private function getExtensionFromResponse(Response $res, $imagePath) private function getExtensionFromResponse(ResponseInterface $res, $imagePath)
{ {
$ext = $this->mimeGuesser->guess($res->getHeader('content-type')); $ext = $this->mimeGuesser->guess(current($res->getHeader('content-type')));
$this->logger->debug('DownloadImages: Checking extension', ['ext' => $ext, 'header' => $res->getHeader('content-type')]); $this->logger->debug('DownloadImages: Checking extension', ['ext' => $ext, 'header' => $res->getHeader('content-type')]);
// ok header doesn't have the extension, try a different way // ok header doesn't have the extension, try a different way

View file

@ -2,16 +2,18 @@
namespace Wallabag\CoreBundle\Helper; namespace Wallabag\CoreBundle\Helper;
use Graby\Ring\Client\SafeCurlHandler; use GuzzleHttp\Client as GuzzleClient;
use GuzzleHttp\Client;
use GuzzleHttp\Cookie\CookieJar; use GuzzleHttp\Cookie\CookieJar;
use GuzzleHttp\Event\SubscriberInterface; use GuzzleHttp\Event\SubscriberInterface;
use Http\Adapter\Guzzle5\Client as GuzzleAdapter;
use Http\Client\HttpClient;
use Http\HttplugBundle\ClientFactory\ClientFactory;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
/** /**
* Builds and configures the Guzzle HTTP client. * Builds and configures the HTTP client.
*/ */
class HttpClientFactory class HttpClientFactory implements ClientFactory
{ {
/** @var [\GuzzleHttp\Event\SubscriberInterface] */ /** @var [\GuzzleHttp\Event\SubscriberInterface] */
private $subscribers = []; private $subscribers = [];
@ -36,29 +38,6 @@ class HttpClientFactory
$this->logger = $logger; $this->logger = $logger;
} }
/**
* @return \GuzzleHttp\Client|null
*/
public function buildHttpClient()
{
$this->logger->log('debug', 'Restricted access config enabled?', ['enabled' => (int) $this->restrictedAccess]);
if (0 === (int) $this->restrictedAccess) {
return;
}
// we clear the cookie to avoid websites who use cookies for analytics
$this->cookieJar->clear();
// need to set the (shared) cookie jar
$client = new Client(['handler' => new SafeCurlHandler(), 'defaults' => ['cookies' => $this->cookieJar]]);
foreach ($this->subscribers as $subscriber) {
$client->getEmitter()->attach($subscriber);
}
return $client;
}
/** /**
* Adds a subscriber to the HTTP client. * Adds a subscriber to the HTTP client.
* *
@ -68,4 +47,34 @@ class HttpClientFactory
{ {
$this->subscribers[] = $subscriber; $this->subscribers[] = $subscriber;
} }
/**
* Input an array of configuration to be able to create a HttpClient.
*
* @param array $config
*
* @return HttpClient
*/
public function createClient(array $config = [])
{
$this->logger->log('debug', 'Restricted access config enabled?', ['enabled' => (int) $this->restrictedAccess]);
if (0 === (int) $this->restrictedAccess) {
return new GuzzleAdapter(new GuzzleClient($config));
}
// we clear the cookie to avoid websites who use cookies for analytics
$this->cookieJar->clear();
if (!isset($config['defaults']['cookies'])) {
// need to set the (shared) cookie jar
$config['defaults']['cookies'] = $this->cookieJar;
}
$guzzle = new GuzzleClient($config);
foreach ($this->subscribers as $subscriber) {
$guzzle->getEmitter()->attach($subscriber);
}
return new GuzzleAdapter($guzzle);
}
} }

View file

@ -21,7 +21,7 @@ class PreparePagerForEntries
/** /**
* @param AdapterInterface $adapter * @param AdapterInterface $adapter
* @param User $user If user isn't logged in, we can force it (like for rss) * @param User $user If user isn't logged in, we can force it (like for feed)
* *
* @return Pagerfanta|null * @return Pagerfanta|null
*/ */

View file

@ -6,6 +6,7 @@ use Psr\Log\LoggerInterface;
use RulerZ\RulerZ; use RulerZ\RulerZ;
use Wallabag\CoreBundle\Entity\Entry; use Wallabag\CoreBundle\Entity\Entry;
use Wallabag\CoreBundle\Entity\Tag; use Wallabag\CoreBundle\Entity\Tag;
use Wallabag\CoreBundle\Entity\TaggingRule;
use Wallabag\CoreBundle\Repository\EntryRepository; use Wallabag\CoreBundle\Repository\EntryRepository;
use Wallabag\CoreBundle\Repository\TagRepository; use Wallabag\CoreBundle\Repository\TagRepository;
use Wallabag\UserBundle\Entity\User; use Wallabag\UserBundle\Entity\User;

View file

@ -0,0 +1,23 @@
<?php
namespace Wallabag\CoreBundle\Helper;
/**
* Hash URLs for privacy and performance.
*/
class UrlHasher
{
/**
* Hash the given url using the given algorithm.
* Hashed url are faster to be retrieved in the database than the real url.
*
* @param string $url
* @param string $algorithm
*
* @return string
*/
public static function hashUrl(string $url, $algorithm = 'sha1')
{
return hash($algorithm, urldecode($url));
}
}

View file

@ -10,12 +10,12 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Wallabag\UserBundle\Entity\User; use Wallabag\UserBundle\Entity\User;
/** /**
* ParamConverter used in the RSS controller to retrieve the right user according to * ParamConverter used in the Feed controller to retrieve the right user according to
* username & token given in the url. * username & token given in the url.
* *
* @see http://stfalcon.com/en/blog/post/symfony2-custom-paramconverter * @see http://stfalcon.com/en/blog/post/symfony2-custom-paramconverter
*/ */
class UsernameRssTokenConverter implements ParamConverterInterface class UsernameFeedTokenConverter implements ParamConverterInterface
{ {
private $registry; private $registry;
@ -67,7 +67,7 @@ class UsernameRssTokenConverter implements ParamConverterInterface
public function apply(Request $request, ParamConverter $configuration) public function apply(Request $request, ParamConverter $configuration)
{ {
$username = $request->attributes->get('username'); $username = $request->attributes->get('username');
$rssToken = $request->attributes->get('token'); $feedToken = $request->attributes->get('token');
if (!$request->attributes->has('username') || !$request->attributes->has('token')) { if (!$request->attributes->has('username') || !$request->attributes->has('token')) {
return false; return false;
@ -78,8 +78,8 @@ class UsernameRssTokenConverter implements ParamConverterInterface
$userRepository = $em->getRepository($configuration->getClass()); $userRepository = $em->getRepository($configuration->getClass());
// Try to find user by its username and config rss_token // Try to find user by its username and config feed_token
$user = $userRepository->findOneByUsernameAndRsstoken($username, $rssToken); $user = $userRepository->findOneByUsernameAndFeedtoken($username, $feedToken);
if (null === $user || !($user instanceof User)) { if (null === $user || !($user instanceof User)) {
throw new NotFoundHttpException(sprintf('%s not found.', $configuration->getClass())); throw new NotFoundHttpException(sprintf('%s not found.', $configuration->getClass()));

View file

@ -3,11 +3,13 @@
namespace Wallabag\CoreBundle\Repository; namespace Wallabag\CoreBundle\Repository;
use Doctrine\ORM\EntityRepository; use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\NoResultException;
use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\QueryBuilder;
use Pagerfanta\Adapter\DoctrineORMAdapter; use Pagerfanta\Adapter\DoctrineORMAdapter;
use Pagerfanta\Pagerfanta; use Pagerfanta\Pagerfanta;
use Wallabag\CoreBundle\Entity\Entry; use Wallabag\CoreBundle\Entity\Entry;
use Wallabag\CoreBundle\Entity\Tag; use Wallabag\CoreBundle\Entity\Tag;
use Wallabag\CoreBundle\Helper\UrlHasher;
class EntryRepository extends EntityRepository class EntryRepository extends EntityRepository
{ {
@ -50,7 +52,7 @@ class EntryRepository extends EntityRepository
public function getBuilderForArchiveByUser($userId) public function getBuilderForArchiveByUser($userId)
{ {
return $this return $this
->getSortedQueryBuilderByUser($userId) ->getSortedQueryBuilderByUser($userId, 'archivedAt', 'desc')
->andWhere('e.isArchived = true') ->andWhere('e.isArchived = true')
; ;
} }
@ -110,8 +112,7 @@ class EntryRepository extends EntityRepository
*/ */
public function getBuilderForUntaggedByUser($userId) public function getBuilderForUntaggedByUser($userId)
{ {
return $this return $this->sortQueryBuilder($this->getRawBuilderForUntaggedByUser($userId));
->sortQueryBuilder($this->getRawBuilderForUntaggedByUser($userId));
} }
/** /**
@ -139,15 +140,30 @@ class EntryRepository extends EntityRepository
* @param string $order * @param string $order
* @param int $since * @param int $since
* @param string $tags * @param string $tags
* @param string $detail 'metadata' or 'full'. Include content field if 'full'
*
* @todo Breaking change: replace default detail=full by detail=metadata in a future version
* *
* @return Pagerfanta * @return Pagerfanta
*/ */
public function findEntries($userId, $isArchived = null, $isStarred = null, $isPublic = null, $sort = 'created', $order = 'asc', $since = 0, $tags = '') public function findEntries($userId, $isArchived = null, $isStarred = null, $isPublic = null, $sort = 'created', $order = 'asc', $since = 0, $tags = '', $detail = 'full')
{ {
if (!\in_array(strtolower($detail), ['full', 'metadata'], true)) {
throw new \Exception('Detail "' . $detail . '" parameter is wrong, allowed: full or metadata');
}
$qb = $this->createQueryBuilder('e') $qb = $this->createQueryBuilder('e')
->leftJoin('e.tags', 't') ->leftJoin('e.tags', 't')
->where('e.user = :userId')->setParameter('userId', $userId); ->where('e.user = :userId')->setParameter('userId', $userId);
if ('metadata' === $detail) {
$fieldNames = $this->getClassMetadata()->getFieldNames();
$fields = array_filter($fieldNames, function ($k) {
return 'content' !== $k;
});
$qb->select(sprintf('partial e.{%s}', implode(',', $fields)));
}
if (null !== $isArchived) { if (null !== $isArchived) {
$qb->andWhere('e.isArchived = :isArchived')->setParameter('isArchived', (bool) $isArchived); $qb->andWhere('e.isArchived = :isArchived')->setParameter('isArchived', (bool) $isArchived);
} }
@ -193,6 +209,8 @@ class EntryRepository extends EntityRepository
$qb->orderBy('e.id', $order); $qb->orderBy('e.id', $order);
} elseif ('updated' === $sort) { } elseif ('updated' === $sort) {
$qb->orderBy('e.updatedAt', $order); $qb->orderBy('e.updatedAt', $order);
} elseif ('archived' === $sort) {
$qb->orderBy('e.archivedAt', $order);
} }
$pagerAdapter = new DoctrineORMAdapter($qb, true, false); $pagerAdapter = new DoctrineORMAdapter($qb, true, false);
@ -324,15 +342,32 @@ class EntryRepository extends EntityRepository
* Find an entry by its url and its owner. * Find an entry by its url and its owner.
* If it exists, return the entry otherwise return false. * If it exists, return the entry otherwise return false.
* *
* @param $url * @param string $url
* @param $userId * @param int $userId
* *
* @return Entry|bool * @return Entry|bool
*/ */
public function findByUrlAndUserId($url, $userId) public function findByUrlAndUserId($url, $userId)
{
return $this->findByHashedUrlAndUserId(
UrlHasher::hashUrl($url),
$userId
);
}
/**
* Find an entry by its hashed url and its owner.
* If it exists, return the entry otherwise return false.
*
* @param string $hashedUrl Url hashed using sha1
* @param int $userId
*
* @return Entry|bool
*/
public function findByHashedUrlAndUserId($hashedUrl, $userId)
{ {
$res = $this->createQueryBuilder('e') $res = $this->createQueryBuilder('e')
->where('e.url = :url')->setParameter('url', urldecode($url)) ->where('e.hashedUrl = :hashed_url')->setParameter('hashed_url', $hashedUrl)
->andWhere('e.user = :user_id')->setParameter('user_id', $userId) ->andWhere('e.user = :user_id')->setParameter('user_id', $userId)
->getQuery() ->getQuery()
->getResult(); ->getResult();
@ -416,8 +451,8 @@ class EntryRepository extends EntityRepository
/** /**
* Find all entries by url and owner. * Find all entries by url and owner.
* *
* @param $url * @param string $url
* @param $userId * @param int $userId
* *
* @return array * @return array
*/ */
@ -430,6 +465,49 @@ class EntryRepository extends EntityRepository
->getResult(); ->getResult();
} }
/**
* Returns a random entry, filtering by status.
*
* @param int $userId
* @param string $type Can be unread, archive, starred, etc
*
* @throws NoResultException
*
* @return Entry
*/
public function getRandomEntry($userId, $type = '')
{
$qb = $this->getQueryBuilderByUser($userId)
->select('e.id');
switch ($type) {
case 'unread':
$qb->andWhere('e.isArchived = false');
break;
case 'archive':
$qb->andWhere('e.isArchived = true');
break;
case 'starred':
$qb->andWhere('e.isStarred = true');
break;
case 'untagged':
$qb->leftJoin('e.tags', 't');
$qb->andWhere('t.id is null');
break;
}
$ids = $qb->getQuery()->getArrayResult();
if (empty($ids)) {
throw new NoResultException();
}
// random select one in the list
$randomId = $ids[mt_rand(0, \count($ids) - 1)]['id'];
return $this->find($randomId);
}
/** /**
* Return a query builder to be used by other getBuilderFor* method. * Return a query builder to be used by other getBuilderFor* method.
* *
@ -468,7 +546,6 @@ class EntryRepository extends EntityRepository
*/ */
private function sortQueryBuilder(QueryBuilder $qb, $sortBy = 'createdAt', $direction = 'desc') private function sortQueryBuilder(QueryBuilder $qb, $sortBy = 'createdAt', $direction = 'desc')
{ {
return $qb return $qb->orderBy(sprintf('e.%s', $sortBy), $direction);
->orderBy(sprintf('e.%s', $sortBy), $direction);
} }
} }

View file

@ -22,10 +22,10 @@ services:
tags: tags:
- { name: form.type } - { name: form.type }
wallabag_core.param_converter.username_rsstoken_converter: wallabag_core.param_converter.username_feed_token_converter:
class: Wallabag\CoreBundle\ParamConverter\UsernameRssTokenConverter class: Wallabag\CoreBundle\ParamConverter\UsernameFeedTokenConverter
tags: tags:
- { name: request.param_converter, converter: username_rsstoken_converter } - { name: request.param_converter, converter: username_feed_token_converter }
arguments: arguments:
- "@doctrine" - "@doctrine"
@ -42,7 +42,7 @@ services:
- -
error_message: '%wallabag_core.fetching_error_message%' error_message: '%wallabag_core.fetching_error_message%'
error_message_title: '%wallabag_core.fetching_error_message_title%' error_message_title: '%wallabag_core.fetching_error_message_title%'
- "@wallabag_core.guzzle.http_client" - "@wallabag_core.http_client"
- "@wallabag_core.graby.config_builder" - "@wallabag_core.graby.config_builder"
calls: calls:
- [ setLogger, [ "@logger" ] ] - [ setLogger, [ "@logger" ] ]
@ -55,9 +55,8 @@ services:
- {} - {}
- "@logger" - "@logger"
wallabag_core.guzzle.http_client: wallabag_core.http_client:
class: GuzzleHttp\ClientInterface alias: 'httplug.client.wallabag_core'
factory: ["@wallabag_core.guzzle.http_client_factory", buildHttpClient]
wallabag_core.guzzle_authenticator.config_builder: wallabag_core.guzzle_authenticator.config_builder:
class: Wallabag\CoreBundle\GuzzleSiteAuthenticator\GrabySiteConfigBuilder class: Wallabag\CoreBundle\GuzzleSiteAuthenticator\GrabySiteConfigBuilder
@ -73,7 +72,7 @@ services:
bd_guzzle_site_authenticator.site_config_builder: bd_guzzle_site_authenticator.site_config_builder:
alias: wallabag_core.guzzle_authenticator.config_builder alias: wallabag_core.guzzle_authenticator.config_builder
wallabag_core.guzzle.http_client_factory: wallabag_core.http_client_factory:
class: Wallabag\CoreBundle\Helper\HttpClientFactory class: Wallabag\CoreBundle\Helper\HttpClientFactory
arguments: arguments:
- "@wallabag_core.guzzle.cookie_jar" - "@wallabag_core.guzzle.cookie_jar"
@ -181,6 +180,7 @@ services:
wallabag_core.exception_controller: wallabag_core.exception_controller:
class: Wallabag\CoreBundle\Controller\ExceptionController class: Wallabag\CoreBundle\Controller\ExceptionController
public: true
arguments: arguments:
- '@twig' - '@twig'
- '%kernel.debug%' - '%kernel.debug%'
@ -211,10 +211,38 @@ services:
- "@logger" - "@logger"
wallabag_core.entry.download_images.client: wallabag_core.entry.download_images.client:
class: GuzzleHttp\Client alias: 'httplug.client.wallabag_core.entry.download_images'
wallabag_core.helper.crypto_proxy: wallabag_core.helper.crypto_proxy:
class: Wallabag\CoreBundle\Helper\CryptoProxy class: Wallabag\CoreBundle\Helper\CryptoProxy
arguments: arguments:
- "%wallabag_core.site_credentials.encryption_key_path%" - "%wallabag_core.site_credentials.encryption_key_path%"
- "@logger" - "@logger"
wallabag_core.command.clean_duplicates:
class: Wallabag\CoreBundle\Command\CleanDuplicatesCommand
tags: ['console.command']
wallabag_core.command.export:
class: Wallabag\CoreBundle\Command\ExportCommand
tags: ['console.command']
wallabag_core.command.install:
class: Wallabag\CoreBundle\Command\InstallCommand
tags: ['console.command']
wallabag_core.command.list_user:
class: Wallabag\CoreBundle\Command\ListUserCommand
tags: ['console.command']
wallabag_core.command.reload_entry:
class: Wallabag\CoreBundle\Command\ReloadEntryCommand
tags: ['console.command']
wallabag_core.command.show_user:
class: Wallabag\CoreBundle\Command\ShowUserCommand
tags: ['console.command']
wallabag_core.command.tag_all:
class: Wallabag\CoreBundle\Command\TagAllCommand
tags: ['console.command']

View file

@ -37,6 +37,7 @@ menu:
add_new_entry: 'Tilføj ny artikel' add_new_entry: 'Tilføj ny artikel'
search: 'Søg' search: 'Søg'
filter_entries: 'Filtrer artikler' filter_entries: 'Filtrer artikler'
# random_entry: Jump to a random entry from that list
# export: 'Export' # export: 'Export'
search_form: search_form:
input_label: 'Indtast søgning' input_label: 'Indtast søgning'
@ -53,11 +54,12 @@ config:
page_title: 'Opsætning' page_title: 'Opsætning'
tab_menu: tab_menu:
settings: 'Indstillinger' settings: 'Indstillinger'
rss: 'RSS' feed: 'RSS'
user_info: 'Brugeroplysninger' user_info: 'Brugeroplysninger'
password: 'Adgangskode' password: 'Adgangskode'
# rules: 'Tagging rules' # rules: 'Tagging rules'
new_user: 'Tilføj bruger' new_user: 'Tilføj bruger'
# reset: 'Reset area'
form: form:
save: 'Gem' save: 'Gem'
form_settings: form_settings:
@ -83,25 +85,33 @@ config:
# help_reading_speed: "wallabag calculates a reading time for each article. You can define here, thanks to this list, if you are a fast or a slow reader. wallabag will recalculate the reading time for each article." # help_reading_speed: "wallabag calculates a reading time for each article. You can define here, thanks to this list, if you are a fast or a slow reader. wallabag will recalculate the reading time for each article."
# help_language: "You can change the language of wallabag interface." # help_language: "You can change the language of wallabag interface."
# help_pocket_consumer_key: "Required for Pocket import. You can create it in your Pocket account." # help_pocket_consumer_key: "Required for Pocket import. You can create it in your Pocket account."
form_rss: form_feed:
description: 'RSS-feeds fra wallabag gør det muligt at læse de artikler, der gemmes i wallabag, med din RSS-læser. Det kræver, at du genererer et token først.' description: 'RSS-feeds fra wallabag gør det muligt at læse de artikler, der gemmes i wallabag, med din RSS-læser. Det kræver, at du genererer et token først.'
token_label: 'RSS-Token' token_label: 'RSS-Token'
no_token: 'Intet token' no_token: 'Intet token'
token_create: 'Opret token' token_create: 'Opret token'
token_reset: 'Nulstil token' token_reset: 'Nulstil token'
rss_links: 'RSS-Links' feed_links: 'RSS-Links'
rss_link: feed_link:
unread: 'Ulæst' unread: 'Ulæst'
starred: 'Favoritter' starred: 'Favoritter'
archive: 'Arkiv' archive: 'Arkiv'
# all: 'All' # all: 'All'
# rss_limit: 'Number of items in the feed' # feed_limit: 'Number of items in the feed'
form_user: form_user:
# two_factor_description: "Enabling two factor authentication means you'll receive an email with a code on every new untrusted connexion" # two_factor_description: "Enabling two factor authentication means you'll receive an email with a code OR need to use an OTP app (like Google Authenticator, Authy or FreeOTP) to get a one time code on every new untrusted connection. You can't choose both option."
name_label: 'Navn' name_label: 'Navn'
email_label: 'Emailadresse' email_label: 'Emailadresse'
# twoFactorAuthentication_label: 'Two factor authentication' two_factor:
# help_twoFactorAuthentication: "If you enable 2FA, each time you want to login to wallabag, you'll receive a code by email." # emailTwoFactor_label: 'Using email (receive a code by email)'
# googleTwoFactor_label: 'Using an OTP app (open the app, like Google Authenticator, Authy or FreeOTP, to get a one time code)'
# table_method: Method
# table_state: State
# table_action: Action
# state_enabled: Enabled
# state_disabled: Disabled
# action_email: Use email
# action_app: Use OTP App
delete: delete:
# title: Delete my account (a.k.a danger zone) # title: Delete my account (a.k.a danger zone)
# description: If you remove your account, ALL your articles, ALL your tags, ALL your annotations and your account will be PERMANENTLY removed (it can't be UNDONE). You'll then be logged out. # description: If you remove your account, ALL your articles, ALL your tags, ALL your annotations and your account will be PERMANENTLY removed (it can't be UNDONE). You'll then be logged out.
@ -159,6 +169,15 @@ config:
# and: 'One rule AND another' # and: 'One rule AND another'
# matches: 'Tests that a <i>subject</i> matches a <i>search</i> (case-insensitive).<br />Example: <code>title matches "football"</code>' # matches: 'Tests that a <i>subject</i> matches a <i>search</i> (case-insensitive).<br />Example: <code>title matches "football"</code>'
# notmatches: 'Tests that a <i>subject</i> doesn''t match match a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>' # notmatches: 'Tests that a <i>subject</i> doesn''t match match a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>'
otp:
# page_title: Two-factor authentication
# app:
# two_factor_code_description_1: You just enabled the OTP two factor authentication, open your OTP app and use that code to get a one time password. It'll disapear after a page reload.
# two_factor_code_description_2: 'You can scan that QR Code with your app:'
# two_factor_code_description_3: 'Also, save these backup codes in a safe place, you can use them in case you lose access to your OTP app:'
# two_factor_code_description_4: 'Test an OTP code from your configured app:'
# cancel: Cancel
# enable: Enable
entry: entry:
# default_title: 'Title of the entry' # default_title: 'Title of the entry'
@ -353,7 +372,7 @@ quickstart:
# title: 'Configure the application' # title: 'Configure the application'
# description: 'In order to have an application which suits you, have a look into the configuration of wallabag.' # description: 'In order to have an application which suits you, have a look into the configuration of wallabag.'
# language: 'Change language and design' # language: 'Change language and design'
# rss: 'Enable RSS feeds' # feed: 'Enable RSS feeds'
# tagging_rules: 'Write rules to automatically tag your articles' # tagging_rules: 'Write rules to automatically tag your articles'
# admin: # admin:
# title: 'Administration' # title: 'Administration'
@ -404,6 +423,8 @@ tag:
new: new:
# add: 'Add' # add: 'Add'
# placeholder: 'You can add several tags, separated by a comma.' # placeholder: 'You can add several tags, separated by a comma.'
rename:
# placeholder: 'You can update tag name.'
# export: # 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>' # 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>'
@ -529,7 +550,8 @@ user:
email_label: 'Emailadresse' email_label: 'Emailadresse'
# enabled_label: 'Enabled' # enabled_label: 'Enabled'
# last_login_label: 'Last login' # last_login_label: 'Last login'
# twofactor_label: Two factor authentication # twofactor_email_label: Two factor authentication by email
# twofactor_google_label: Two factor authentication by OTP app
# save: Save # save: Save
# delete: Delete # delete: Delete
# delete_confirm: Are you sure? # delete_confirm: Are you sure?
@ -567,10 +589,10 @@ flashes:
password_updated: 'Adgangskode opdateret' password_updated: 'Adgangskode opdateret'
# password_not_updated_demo: "In demonstration mode, you can't change password for this user." # password_not_updated_demo: "In demonstration mode, you can't change password for this user."
user_updated: 'Oplysninger opdateret' user_updated: 'Oplysninger opdateret'
rss_updated: 'RSS-oplysninger opdateret' feed_updated: 'RSS-oplysninger opdateret'
# tagging_rules_updated: 'Tagging rules updated' # tagging_rules_updated: 'Tagging rules updated'
# tagging_rules_deleted: 'Tagging rule deleted' # tagging_rules_deleted: 'Tagging rule deleted'
# rss_token_updated: 'RSS token updated' # feed_token_updated: 'RSS token updated'
# annotations_reset: Annotations reset # annotations_reset: Annotations reset
# tags_reset: Tags reset # tags_reset: Tags reset
# entries_reset: Entries reset # entries_reset: Entries reset
@ -588,9 +610,11 @@ flashes:
entry_starred: 'Artikel markeret som favorit' entry_starred: 'Artikel markeret som favorit'
entry_unstarred: 'Artikel ikke længere markeret som favorit' entry_unstarred: 'Artikel ikke længere markeret som favorit'
entry_deleted: 'Artikel slettet' entry_deleted: 'Artikel slettet'
# no_random_entry: 'No article with these criterias was found'
tag: tag:
notice: notice:
# tag_added: 'Tag added' # tag_added: 'Tag added'
# tag_renamed: 'Tag renamed'
import: import:
notice: notice:
# failed: 'Import failed, please try again.' # failed: 'Import failed, please try again.'

View file

@ -37,6 +37,7 @@ menu:
add_new_entry: 'Neuen Artikel hinzufügen' add_new_entry: 'Neuen Artikel hinzufügen'
search: 'Suche' search: 'Suche'
filter_entries: 'Artikel filtern' filter_entries: 'Artikel filtern'
# random_entry: Jump to a random entry from that list
export: 'Exportieren' export: 'Exportieren'
search_form: search_form:
input_label: 'Suchbegriff hier eingeben' input_label: 'Suchbegriff hier eingeben'
@ -53,11 +54,12 @@ config:
page_title: 'Einstellungen' page_title: 'Einstellungen'
tab_menu: tab_menu:
settings: 'Einstellungen' settings: 'Einstellungen'
rss: 'RSS' feed: 'RSS'
user_info: 'Benutzerinformation' user_info: 'Benutzerinformation'
password: 'Kennwort' password: 'Kennwort'
rules: 'Tagging-Regeln' rules: 'Tagging-Regeln'
new_user: 'Benutzer hinzufügen' new_user: 'Benutzer hinzufügen'
reset: 'Zurücksetzen'
form: form:
save: 'Speichern' save: 'Speichern'
form_settings: form_settings:
@ -83,25 +85,33 @@ config:
help_reading_speed: "wallabag berechnet eine Lesezeit pro Artikel. Hier kannst du definieren, ob du ein schneller oder langsamer Leser bist. wallabag wird die Lesezeiten danach neu berechnen." help_reading_speed: "wallabag berechnet eine Lesezeit pro Artikel. Hier kannst du definieren, ob du ein schneller oder langsamer Leser bist. wallabag wird die Lesezeiten danach neu berechnen."
help_language: "Du kannst die Sprache der wallabag-Oberfläche ändern." help_language: "Du kannst die Sprache der wallabag-Oberfläche ändern."
help_pocket_consumer_key: "Nötig für den Pocket-Import. Du kannst ihn in deinem Pocket account einrichten." help_pocket_consumer_key: "Nötig für den Pocket-Import. Du kannst ihn in deinem Pocket account einrichten."
form_rss: form_feed:
description: 'Die RSS-Feeds von wallabag erlauben es dir, deine gespeicherten Artikel mit deinem bevorzugten RSS-Reader zu lesen. Vorher musst du jedoch einen Token erstellen.' description: 'Die RSS-Feeds von wallabag erlauben es dir, deine gespeicherten Artikel mit deinem bevorzugten RSS-Reader zu lesen. Vorher musst du jedoch einen Token erstellen.'
token_label: 'RSS-Token' token_label: 'RSS-Token'
no_token: 'Kein Token' no_token: 'Kein Token'
token_create: 'Token erstellen' token_create: 'Token erstellen'
token_reset: 'Token zurücksetzen' token_reset: 'Token zurücksetzen'
rss_links: 'RSS-Links' feed_links: 'RSS-Links'
rss_link: feed_link:
unread: 'Ungelesene' unread: 'Ungelesene'
starred: 'Favoriten' starred: 'Favoriten'
archive: 'Archivierte' archive: 'Archivierte'
all: 'Alle' all: 'Alle'
rss_limit: 'Anzahl der Einträge pro Feed' feed_limit: 'Anzahl der Einträge pro Feed'
form_user: form_user:
two_factor_description: "Wenn du die Zwei-Faktor-Authentifizierung aktivierst, erhältst du eine E-Mail mit einem Code bei jeder nicht vertrauenswürdigen Verbindung" # two_factor_description: "Enabling two factor authentication means you'll receive an email with a code OR need to use an OTP app (like Google Authenticator, Authy or FreeOTP) to get a one time code on every new untrusted connection. You can't choose both option."
name_label: 'Name' name_label: 'Name'
email_label: 'E-Mail-Adresse' email_label: 'E-Mail-Adresse'
twoFactorAuthentication_label: 'Zwei-Faktor-Authentifizierung' two_factor:
help_twoFactorAuthentication: "Wenn du 2FA aktivierst, wirst du bei jedem Login einen Code per E-Mail bekommen." # emailTwoFactor_label: 'Using email (receive a code by email)'
# googleTwoFactor_label: 'Using an OTP app (open the app, like Google Authenticator, Authy or FreeOTP, to get a one time code)'
# table_method: Method
# table_state: State
# table_action: Action
# state_enabled: Enabled
# state_disabled: Disabled
# action_email: Use email
# action_app: Use OTP App
delete: delete:
title: 'Lösche mein Konto (a.k.a Gefahrenzone)' title: 'Lösche mein Konto (a.k.a Gefahrenzone)'
description: 'Wenn du dein Konto löschst, werden ALL deine Artikel, ALL deine Tags, ALL deine Anmerkungen und dein Konto dauerhaft gelöscht (kann NICHT RÜCKGÄNGIG gemacht werden). Du wirst anschließend ausgeloggt.' description: 'Wenn du dein Konto löschst, werden ALL deine Artikel, ALL deine Tags, ALL deine Anmerkungen und dein Konto dauerhaft gelöscht (kann NICHT RÜCKGÄNGIG gemacht werden). Du wirst anschließend ausgeloggt.'
@ -353,7 +363,7 @@ quickstart:
title: 'Anwendung konfigurieren' title: 'Anwendung konfigurieren'
description: 'Um die Applikation für dich anzupassen, schau in die Konfiguration von wallabag.' description: 'Um die Applikation für dich anzupassen, schau in die Konfiguration von wallabag.'
language: 'Sprache und Design ändern' language: 'Sprache und Design ändern'
rss: 'RSS-Feeds aktivieren' feed: 'RSS-Feeds aktivieren'
tagging_rules: 'Schreibe Regeln, um deine Beiträge automatisch zu taggen (verschlagworten)' tagging_rules: 'Schreibe Regeln, um deine Beiträge automatisch zu taggen (verschlagworten)'
admin: admin:
title: 'Administration' title: 'Administration'
@ -404,6 +414,8 @@ tag:
new: new:
add: 'Hinzufügen' add: 'Hinzufügen'
placeholder: 'Du kannst verschiedene Tags, getrennt von einem Komma, hinzufügen.' placeholder: 'Du kannst verschiedene Tags, getrennt von einem Komma, hinzufügen.'
rename:
# placeholder: 'You can update tag name.'
export: 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>' 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>'
@ -529,7 +541,8 @@ user:
email_label: 'E-Mail-Adresse' email_label: 'E-Mail-Adresse'
enabled_label: 'Aktiviert' enabled_label: 'Aktiviert'
last_login_label: 'Letzter Login' last_login_label: 'Letzter Login'
twofactor_label: 'Zwei-Faktor-Authentifizierung' # twofactor_email_label: Two factor authentication by email
# twofactor_google_label: Two factor authentication by OTP app
save: 'Speichern' save: 'Speichern'
delete: 'Löschen' delete: 'Löschen'
delete_confirm: 'Bist du sicher?' delete_confirm: 'Bist du sicher?'
@ -567,14 +580,14 @@ flashes:
password_updated: 'Kennwort aktualisiert' password_updated: 'Kennwort aktualisiert'
password_not_updated_demo: 'Im Testmodus kannst du das Kennwort nicht ändern.' password_not_updated_demo: 'Im Testmodus kannst du das Kennwort nicht ändern.'
user_updated: 'Information aktualisiert' user_updated: 'Information aktualisiert'
rss_updated: 'RSS-Informationen aktualisiert' feed_updated: 'RSS-Informationen aktualisiert'
tagging_rules_updated: 'Tagging-Regeln aktualisiert' tagging_rules_updated: 'Tagging-Regeln aktualisiert'
tagging_rules_deleted: 'Tagging-Regel gelöscht' tagging_rules_deleted: 'Tagging-Regel gelöscht'
rss_token_updated: 'RSS-Token aktualisiert' feed_token_updated: 'RSS-Token aktualisiert'
annotations_reset: 'Anmerkungen zurücksetzen' annotations_reset: Anmerkungen zurücksetzen
tags_reset: 'Tags zurücksetzen' tags_reset: Tags zurücksetzen
entries_reset: 'Einträge zurücksetzen' entries_reset: Einträge zurücksetzen
archived_reset: 'Archiverte Einträge zurücksetzen' archived_reset: Archiverte Einträge zurücksetzen
entry: entry:
notice: notice:
entry_already_saved: 'Eintrag bereits am %date% gespeichert' entry_already_saved: 'Eintrag bereits am %date% gespeichert'
@ -588,9 +601,11 @@ flashes:
entry_starred: 'Eintrag favorisiert' entry_starred: 'Eintrag favorisiert'
entry_unstarred: 'Eintrag defavorisiert' entry_unstarred: 'Eintrag defavorisiert'
entry_deleted: 'Eintrag gelöscht' entry_deleted: 'Eintrag gelöscht'
# no_random_entry: 'No article with these criterias was found'
tag: tag:
notice: notice:
tag_added: 'Tag hinzugefügt' tag_added: 'Tag hinzugefügt'
#tag_renamed: 'Tag renamed'
import: import:
notice: notice:
failed: 'Import fehlgeschlagen, bitte erneut probieren.' failed: 'Import fehlgeschlagen, bitte erneut probieren.'

View file

@ -37,6 +37,7 @@ menu:
add_new_entry: 'Add a new entry' add_new_entry: 'Add a new entry'
search: 'Search' search: 'Search'
filter_entries: 'Filter entries' filter_entries: 'Filter entries'
random_entry: Jump to a random entry from that list
export: 'Export' export: 'Export'
search_form: search_form:
input_label: 'Enter your search here' input_label: 'Enter your search here'
@ -53,11 +54,12 @@ config:
page_title: 'Config' page_title: 'Config'
tab_menu: tab_menu:
settings: 'Settings' settings: 'Settings'
rss: 'RSS' feed: 'Feeds'
user_info: 'User information' user_info: 'User information'
password: 'Password' password: 'Password'
rules: 'Tagging rules' rules: 'Tagging rules'
new_user: 'Add a user' new_user: 'Add a user'
reset: 'Reset area'
form: form:
save: 'Save' save: 'Save'
form_settings: form_settings:
@ -83,25 +85,33 @@ config:
help_reading_speed: "wallabag calculates a reading time for each article. You can define here, thanks to this list, if you are a fast or a slow reader. wallabag will recalculate the reading time for each article." help_reading_speed: "wallabag calculates a reading time for each article. You can define here, thanks to this list, if you are a fast or a slow reader. wallabag will recalculate the reading time for each article."
help_language: "You can change the language of wallabag interface." help_language: "You can change the language of wallabag interface."
help_pocket_consumer_key: "Required for Pocket import. You can create it in your Pocket account." help_pocket_consumer_key: "Required for Pocket import. You can create it in your Pocket account."
form_rss: form_feed:
description: 'RSS feeds provided by wallabag allow you to read your saved articles with your favourite RSS reader. You need to generate a token first.' description: 'Atom feeds provided by wallabag allow you to read your saved articles with your favourite Atom reader. You need to generate a token first.'
token_label: 'RSS token' token_label: 'Feed token'
no_token: 'No token' no_token: 'No token'
token_create: 'Create your token' token_create: 'Create your token'
token_reset: 'Regenerate your token' token_reset: 'Regenerate your token'
rss_links: 'RSS links' feed_links: 'Feed links'
rss_link: feed_link:
unread: 'Unread' unread: 'Unread'
starred: 'Starred' starred: 'Starred'
archive: 'Archived' archive: 'Archived'
all: 'All' all: 'All'
rss_limit: 'Number of items in the feed' feed_limit: 'Number of items in the feed'
form_user: form_user:
two_factor_description: "Enabling two factor authentication means you'll receive an email with a code on every new untrusted connection." two_factor_description: "Enabling two factor authentication means you'll receive an email with a code OR need to use an OTP app (like Google Authenticator, Authy or FreeOTP) to get a one time code on every new untrusted connection. You can't choose both option."
name_label: 'Name' name_label: 'Name'
email_label: 'Email' email_label: 'Email'
twoFactorAuthentication_label: 'Two factor authentication' two_factor:
help_twoFactorAuthentication: "If you enable 2FA, each time you want to login to wallabag, you'll receive a code by email." emailTwoFactor_label: 'Using email (receive a code by email)'
googleTwoFactor_label: 'Using an OTP app (open the app, like Google Authenticator, Authy or FreeOTP, to get a one time code)'
table_method: Method
table_state: State
table_action: Action
state_enabled: Enabled
state_disabled: Disabled
action_email: Use email
action_app: Use OTP App
delete: delete:
title: Delete my account (a.k.a danger zone) title: Delete my account (a.k.a danger zone)
description: If you remove your account, ALL your articles, ALL your tags, ALL your annotations and your account will be PERMANENTLY removed (it can't be UNDONE). You'll then be logged out. description: If you remove your account, ALL your articles, ALL your tags, ALL your annotations and your account will be PERMANENTLY removed (it can't be UNDONE). You'll then be logged out.
@ -159,6 +169,15 @@ config:
and: 'One rule AND another' and: 'One rule AND another'
matches: 'Tests that a <i>subject</i> matches a <i>search</i> (case-insensitive).<br />Example: <code>title matches "football"</code>' matches: 'Tests that a <i>subject</i> matches a <i>search</i> (case-insensitive).<br />Example: <code>title matches "football"</code>'
notmatches: 'Tests that a <i>subject</i> doesn''t match match a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>' notmatches: 'Tests that a <i>subject</i> doesn''t match match a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>'
otp:
page_title: Two-factor authentication
app:
two_factor_code_description_1: You just enabled the OTP two factor authentication, open your OTP app and use that code to get a one time password. It'll disapear after a page reload.
two_factor_code_description_2: 'You can scan that QR Code with your app:'
two_factor_code_description_3: 'Also, save these backup codes in a safe place, you can use them in case you lose access to your OTP app:'
two_factor_code_description_4: 'Test an OTP code from your configured app:'
cancel: Cancel
enable: Enable
entry: entry:
default_title: 'Title of the entry' default_title: 'Title of the entry'
@ -353,7 +372,7 @@ quickstart:
title: 'Configure the application' title: 'Configure the application'
description: 'In order to have an application which suits you, have a look into the configuration of wallabag.' description: 'In order to have an application which suits you, have a look into the configuration of wallabag.'
language: 'Change language and design' language: 'Change language and design'
rss: 'Enable RSS feeds' feed: 'Enable feeds'
tagging_rules: 'Write rules to automatically tag your articles' tagging_rules: 'Write rules to automatically tag your articles'
admin: admin:
title: 'Administration' title: 'Administration'
@ -404,6 +423,8 @@ tag:
new: new:
add: 'Add' add: 'Add'
placeholder: 'You can add several tags, separated by a comma.' placeholder: 'You can add several tags, separated by a comma.'
rename:
placeholder: 'You can update tag name.'
export: 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>' 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>'
@ -529,7 +550,8 @@ user:
email_label: 'Email' email_label: 'Email'
enabled_label: 'Enabled' enabled_label: 'Enabled'
last_login_label: 'Last login' last_login_label: 'Last login'
twofactor_label: Two factor authentication twofactor_email_label: Two factor authentication by email
twofactor_google_label: Two factor authentication by OTP app
save: Save save: Save
delete: Delete delete: Delete
delete_confirm: Are you sure? delete_confirm: Are you sure?
@ -567,14 +589,15 @@ flashes:
password_updated: 'Password updated' password_updated: 'Password updated'
password_not_updated_demo: "In demonstration mode, you can't change password for this user." password_not_updated_demo: "In demonstration mode, you can't change password for this user."
user_updated: 'Information updated' user_updated: 'Information updated'
rss_updated: 'RSS information updated' feed_updated: 'Feed information updated'
tagging_rules_updated: 'Tagging rules updated' tagging_rules_updated: 'Tagging rules updated'
tagging_rules_deleted: 'Tagging rule deleted' tagging_rules_deleted: 'Tagging rule deleted'
rss_token_updated: 'RSS token updated' feed_token_updated: 'Feed token updated'
annotations_reset: Annotations reset annotations_reset: Annotations reset
tags_reset: Tags reset tags_reset: Tags reset
entries_reset: Entries reset entries_reset: Entries reset
archived_reset: Archived entries deleted archived_reset: Archived entries deleted
otp_enabled: Two-factor authentication enabled
entry: entry:
notice: notice:
entry_already_saved: 'Entry already saved on %date%' entry_already_saved: 'Entry already saved on %date%'
@ -588,9 +611,11 @@ flashes:
entry_starred: 'Entry starred' entry_starred: 'Entry starred'
entry_unstarred: 'Entry unstarred' entry_unstarred: 'Entry unstarred'
entry_deleted: 'Entry deleted' entry_deleted: 'Entry deleted'
no_random_entry: 'No article with these criterias was found'
tag: tag:
notice: notice:
tag_added: 'Tag added' tag_added: 'Tag added'
tag_renamed: 'Tag renamed'
import: import:
notice: notice:
failed: 'Import failed, please try again.' failed: 'Import failed, please try again.'

View file

@ -37,6 +37,7 @@ menu:
add_new_entry: 'Añadir un nuevo artículo' add_new_entry: 'Añadir un nuevo artículo'
search: 'Buscar' search: 'Buscar'
filter_entries: 'Filtrar los artículos' filter_entries: 'Filtrar los artículos'
# random_entry: Jump to a random entry from that list
export: 'Exportar' export: 'Exportar'
search_form: search_form:
input_label: 'Introduzca su búsqueda aquí' input_label: 'Introduzca su búsqueda aquí'
@ -53,11 +54,12 @@ config:
page_title: 'Configuración' page_title: 'Configuración'
tab_menu: tab_menu:
settings: 'Configuración' settings: 'Configuración'
rss: 'RSS' feed: 'RSS'
user_info: 'Información de usuario' user_info: 'Información de usuario'
password: 'Contraseña' password: 'Contraseña'
rules: 'Reglas de etiquetado automáticas' rules: 'Reglas de etiquetado automáticas'
new_user: 'Añadir un usuario' new_user: 'Añadir un usuario'
reset: 'Reiniciar mi cuenta'
form: form:
save: 'Guardar' save: 'Guardar'
form_settings: form_settings:
@ -83,25 +85,33 @@ config:
help_reading_speed: "wallabag calcula un tiempo de lectura para cada artículo. Puedes definir aquí, gracias a esta lista, si eres un lector rápido o lento. wallabag recalculará el tiempo de lectura para cada artículo." help_reading_speed: "wallabag calcula un tiempo de lectura para cada artículo. Puedes definir aquí, gracias a esta lista, si eres un lector rápido o lento. wallabag recalculará el tiempo de lectura para cada artículo."
help_language: "Puedes cambiar el idioma de la interfaz de wallabag." help_language: "Puedes cambiar el idioma de la interfaz de wallabag."
help_pocket_consumer_key: "Requerido para la importación desde Pocket. Puedes crearla en tu cuenta de Pocket." help_pocket_consumer_key: "Requerido para la importación desde Pocket. Puedes crearla en tu cuenta de Pocket."
form_rss: form_feed:
description: 'Los feeds RSS de wallabag permiten leer los artículos guardados con su lector RSS favorito. Primero necesitas generar un token.' description: 'Los feeds RSS de wallabag permiten leer los artículos guardados con su lector RSS favorito. Primero necesitas generar un token.'
token_label: 'Token RSS' token_label: 'Token RSS'
no_token: 'Sin token' no_token: 'Sin token'
token_create: 'Crear token' token_create: 'Crear token'
token_reset: 'Reiniciar token' token_reset: 'Reiniciar token'
rss_links: 'URLs de feeds RSS' feed_links: 'URLs de feeds RSS'
rss_link: feed_link:
unread: 'sin leer' unread: 'sin leer'
starred: 'favoritos' starred: 'favoritos'
archive: 'archivados' archive: 'archivados'
# all: 'All' # all: 'All'
rss_limit: 'Límite de artículos en feed RSS' feed_limit: 'Límite de artículos en feed RSS'
form_user: form_user:
two_factor_description: "Con la autenticación en dos pasos recibirá código por e-mail en cada nueva conexión que no sea de confianza." # two_factor_description: "Enabling two factor authentication means you'll receive an email with a code OR need to use an OTP app (like Google Authenticator, Authy or FreeOTP) to get a one time code on every new untrusted connection. You can't choose both option."
name_label: 'Nombre' name_label: 'Nombre'
email_label: 'Dirección de e-mail' email_label: 'Dirección de e-mail'
twoFactorAuthentication_label: 'Autenticación en dos pasos' two_factor:
help_twoFactorAuthentication: "Si activas la autenticación en dos pasos, cada vez que quieras iniciar sesión en wallabag recibirás un código por e-mail." # emailTwoFactor_label: 'Using email (receive a code by email)'
# googleTwoFactor_label: 'Using an OTP app (open the app, like Google Authenticator, Authy or FreeOTP, to get a one time code)'
# table_method: Method
# table_state: State
# table_action: Action
# state_enabled: Enabled
# state_disabled: Disabled
# action_email: Use email
# action_app: Use OTP App
delete: delete:
title: Eliminar mi cuenta (Zona peligrosa) title: Eliminar mi cuenta (Zona peligrosa)
description: Si eliminas tu cuenta, TODOS tus artículos, TODAS tus etiquetas, TODAS tus anotaciones y tu cuenta serán eliminadas de forma PERMANENTE (no se puede deshacer). Después serás desconectado. description: Si eliminas tu cuenta, TODOS tus artículos, TODAS tus etiquetas, TODAS tus anotaciones y tu cuenta serán eliminadas de forma PERMANENTE (no se puede deshacer). Después serás desconectado.
@ -159,6 +169,15 @@ config:
and: 'Una regla Y la otra' and: 'Una regla Y la otra'
matches: 'Prueba si un <i>sujeto</i> corresponde a una <i>búsqueda</i> (insensible a mayusculas).<br />Ejemplo : <code>title matches "fútbol"</code>' matches: 'Prueba si un <i>sujeto</i> corresponde a una <i>búsqueda</i> (insensible a mayusculas).<br />Ejemplo : <code>title matches "fútbol"</code>'
# notmatches: 'Tests that a <i>subject</i> doesn''t match match a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>' # notmatches: 'Tests that a <i>subject</i> doesn''t match match a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>'
otp:
# page_title: Two-factor authentication
# app:
# two_factor_code_description_1: You just enabled the OTP two factor authentication, open your OTP app and use that code to get a one time password. It'll disapear after a page reload.
# two_factor_code_description_2: 'You can scan that QR Code with your app:'
# two_factor_code_description_3: 'Also, save these backup codes in a safe place, you can use them in case you lose access to your OTP app:'
# two_factor_code_description_4: 'Test an OTP code from your configured app:'
# cancel: Cancel
# enable: Enable
entry: entry:
default_title: 'Título del artículo' default_title: 'Título del artículo'
@ -353,7 +372,7 @@ quickstart:
title: 'Configure la aplicación' title: 'Configure la aplicación'
description: 'Para que la aplicación se ajuste a tus necesidades, echa un vistazo a la configuración de wallabag.' description: 'Para que la aplicación se ajuste a tus necesidades, echa un vistazo a la configuración de wallabag.'
language: 'Cambie el idioma y el diseño' language: 'Cambie el idioma y el diseño'
rss: 'Activar los feeds RSS' feed: 'Activar los feeds RSS'
tagging_rules: 'Escribe reglas para etiquetar automáticamente tus artículos' tagging_rules: 'Escribe reglas para etiquetar automáticamente tus artículos'
admin: admin:
title: 'Administración' title: 'Administración'
@ -404,6 +423,8 @@ tag:
new: new:
add: 'Añadir' add: 'Añadir'
placeholder: 'Puedes añadir varias etiquetas, separadas por una coma.' placeholder: 'Puedes añadir varias etiquetas, separadas por una coma.'
rename:
# placeholder: 'You can update tag name.'
# export: # 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>' # 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>'
@ -529,7 +550,8 @@ user:
email_label: 'E-mail' email_label: 'E-mail'
enabled_label: 'Activado' enabled_label: 'Activado'
last_login_label: 'Último inicio de sesión' last_login_label: 'Último inicio de sesión'
twofactor_label: Autenticación en dos pasos # twofactor_email_label: Two factor authentication by email
# twofactor_google_label: Two factor authentication by OTP app
save: Guardar save: Guardar
delete: Eliminar delete: Eliminar
delete_confirm: ¿Estás seguro? delete_confirm: ¿Estás seguro?
@ -567,10 +589,10 @@ flashes:
password_updated: 'Contraseña actualizada' password_updated: 'Contraseña actualizada'
password_not_updated_demo: "En el modo demo, no puede cambiar la contraseña del usuario." password_not_updated_demo: "En el modo demo, no puede cambiar la contraseña del usuario."
user_updated: 'Información actualizada' user_updated: 'Información actualizada'
rss_updated: 'Configuración RSS actualizada' feed_updated: 'Configuración RSS actualizada'
tagging_rules_updated: 'Regla de etiquetado actualizada' tagging_rules_updated: 'Regla de etiquetado actualizada'
tagging_rules_deleted: 'Regla de etiquetado eliminada' tagging_rules_deleted: 'Regla de etiquetado eliminada'
rss_token_updated: 'Token RSS actualizado' feed_token_updated: 'Token RSS actualizado'
annotations_reset: Anotaciones reiniciadas annotations_reset: Anotaciones reiniciadas
tags_reset: Etiquetas reiniciadas tags_reset: Etiquetas reiniciadas
entries_reset: Artículos reiniciados entries_reset: Artículos reiniciados
@ -588,9 +610,11 @@ flashes:
entry_starred: 'Artículo marcado como favorito' entry_starred: 'Artículo marcado como favorito'
entry_unstarred: 'Artículo desmarcado como favorito' entry_unstarred: 'Artículo desmarcado como favorito'
entry_deleted: 'Artículo eliminado' entry_deleted: 'Artículo eliminado'
# no_random_entry: 'No article with these criterias was found'
tag: tag:
notice: notice:
tag_added: 'Etiqueta añadida' tag_added: 'Etiqueta añadida'
# tag_renamed: 'Tag renamed'
import: import:
notice: notice:
failed: 'Importación fallida, por favor, inténtelo de nuevo.' failed: 'Importación fallida, por favor, inténtelo de nuevo.'

View file

@ -37,6 +37,7 @@ menu:
add_new_entry: 'افزودن مقالهٔ تازه' add_new_entry: 'افزودن مقالهٔ تازه'
search: 'جستجو' search: 'جستجو'
filter_entries: 'فیلترکردن مقاله‌ها' filter_entries: 'فیلترکردن مقاله‌ها'
# random_entry: Jump to a random entry from that list
export: 'برون‌بری' export: 'برون‌بری'
search_form: search_form:
input_label: 'جستجوی خود را این‌جا بنویسید:' input_label: 'جستجوی خود را این‌جا بنویسید:'
@ -53,11 +54,12 @@ config:
page_title: 'پیکربندی' page_title: 'پیکربندی'
tab_menu: tab_menu:
settings: 'تنظیمات' settings: 'تنظیمات'
rss: 'آر-اس-اس' feed: 'آر-اس-اس'
user_info: 'اطلاعات کاربر' user_info: 'اطلاعات کاربر'
password: 'رمز' password: 'رمز'
rules: 'برچسب‌گذاری خودکار' rules: 'برچسب‌گذاری خودکار'
new_user: 'افزودن کاربر' new_user: 'افزودن کاربر'
# reset: 'Reset area'
form: form:
save: 'ذخیره' save: 'ذخیره'
form_settings: form_settings:
@ -83,25 +85,33 @@ config:
# help_reading_speed: "wallabag calculates a reading time for each article. You can define here, thanks to this list, if you are a fast or a slow reader. wallabag will recalculate the reading time for each article." # help_reading_speed: "wallabag calculates a reading time for each article. You can define here, thanks to this list, if you are a fast or a slow reader. wallabag will recalculate the reading time for each article."
# help_language: "You can change the language of wallabag interface." # help_language: "You can change the language of wallabag interface."
# help_pocket_consumer_key: "Required for Pocket import. You can create it in your Pocket account." # help_pocket_consumer_key: "Required for Pocket import. You can create it in your Pocket account."
form_rss: form_feed:
description: 'با خوراک آر-اس-اس که wallabag در اختیارتان می‌گذارد، می‌توانید مقاله‌های ذخیره‌شده را در نرم‌افزار آر-اس-اس دلخواه خود بخوانید. برای این کار نخست باید یک کد بسازید.' description: 'با خوراک آر-اس-اس که wallabag در اختیارتان می‌گذارد، می‌توانید مقاله‌های ذخیره‌شده را در نرم‌افزار آر-اس-اس دلخواه خود بخوانید. برای این کار نخست باید یک کد بسازید.'
token_label: 'کد آر-اس-اس' token_label: 'کد آر-اس-اس'
no_token: 'بدون کد' no_token: 'بدون کد'
token_create: 'کد خود را بسازید' token_create: 'کد خود را بسازید'
token_reset: 'بازنشانی کد' token_reset: 'بازنشانی کد'
rss_links: 'پیوند آر-اس-اس' feed_links: 'پیوند آر-اس-اس'
rss_link: feed_link:
unread: 'خوانده‌نشده' unread: 'خوانده‌نشده'
starred: 'برگزیده' starred: 'برگزیده'
archive: 'بایگانی' archive: 'بایگانی'
# all: 'All' # all: 'All'
rss_limit: 'محدودیت آر-اس-اس' feed_limit: 'محدودیت آر-اس-اس'
form_user: form_user:
two_factor_description: "با فعال‌کردن تأیید ۲مرحله‌ای هر بار که اتصال تأییدنشده‌ای برقرار شد، به شما یک کد از راه ایمیل فرستاده می‌شود" # two_factor_description: "Enabling two factor authentication means you'll receive an email with a code OR need to use an OTP app (like Google Authenticator, Authy or FreeOTP) to get a one time code on every new untrusted connection. You can't choose both option."
name_label: 'نام' name_label: 'نام'
email_label: 'نشانی ایمیل' email_label: 'نشانی ایمیل'
twoFactorAuthentication_label: 'تأیید ۲مرحله‌ای' two_factor:
# help_twoFactorAuthentication: "If you enable 2FA, each time you want to login to wallabag, you'll receive a code by email." # emailTwoFactor_label: 'Using email (receive a code by email)'
# googleTwoFactor_label: 'Using an OTP app (open the app, like Google Authenticator, Authy or FreeOTP, to get a one time code)'
# table_method: Method
# table_state: State
# table_action: Action
# state_enabled: Enabled
# state_disabled: Disabled
# action_email: Use email
# action_app: Use OTP App
delete: delete:
# title: Delete my account (a.k.a danger zone) # title: Delete my account (a.k.a danger zone)
# description: If you remove your account, ALL your articles, ALL your tags, ALL your annotations and your account will be PERMANENTLY removed (it can't be UNDONE). You'll then be logged out. # description: If you remove your account, ALL your articles, ALL your tags, ALL your annotations and your account will be PERMANENTLY removed (it can't be UNDONE). You'll then be logged out.
@ -159,6 +169,15 @@ config:
# and: 'One rule AND another' # and: 'One rule AND another'
# matches: 'Tests that a <i>subject</i> matches a <i>search</i> (case-insensitive).<br />Example: <code>title matches "football"</code>' # matches: 'Tests that a <i>subject</i> matches a <i>search</i> (case-insensitive).<br />Example: <code>title matches "football"</code>'
# notmatches: 'Tests that a <i>subject</i> doesn''t match match a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>' # notmatches: 'Tests that a <i>subject</i> doesn''t match match a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>'
otp:
# page_title: Two-factor authentication
# app:
# two_factor_code_description_1: You just enabled the OTP two factor authentication, open your OTP app and use that code to get a one time password. It'll disapear after a page reload.
# two_factor_code_description_2: 'You can scan that QR Code with your app:'
# two_factor_code_description_3: 'Also, save these backup codes in a safe place, you can use them in case you lose access to your OTP app:'
# two_factor_code_description_4: 'Test an OTP code from your configured app:'
# cancel: Cancel
# enable: Enable
entry: entry:
# default_title: 'Title of the entry' # default_title: 'Title of the entry'
@ -353,7 +372,7 @@ quickstart:
title: 'برنامه را تنظیم کنید' title: 'برنامه را تنظیم کنید'
# description: 'In order to have an application which suits you, have a look into the configuration of wallabag.' # description: 'In order to have an application which suits you, have a look into the configuration of wallabag.'
language: 'زبان و نمای برنامه را تغییر دهید' language: 'زبان و نمای برنامه را تغییر دهید'
rss: 'خوراک آر-اس-اس را فعال کنید' feed: 'خوراک آر-اس-اس را فعال کنید'
tagging_rules: 'قانون‌های برچسب‌گذاری خودکار مقاله‌هایتان را تعریف کنید' tagging_rules: 'قانون‌های برچسب‌گذاری خودکار مقاله‌هایتان را تعریف کنید'
admin: admin:
title: 'مدیریت' title: 'مدیریت'
@ -404,6 +423,8 @@ tag:
new: new:
# add: 'Add' # add: 'Add'
# placeholder: 'You can add several tags, separated by a comma.' # placeholder: 'You can add several tags, separated by a comma.'
rename:
# placeholder: 'You can update tag name.'
# export: # 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>' # 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>'
@ -529,7 +550,8 @@ user:
email_label: 'نشانی ایمیل' email_label: 'نشانی ایمیل'
# enabled_label: 'Enabled' # enabled_label: 'Enabled'
# last_login_label: 'Last login' # last_login_label: 'Last login'
# twofactor_label: Two factor authentication # twofactor_email_label: Two factor authentication by email
# twofactor_google_label: Two factor authentication by OTP app
# save: Save # save: Save
# delete: Delete # delete: Delete
# delete_confirm: Are you sure? # delete_confirm: Are you sure?
@ -567,10 +589,10 @@ flashes:
password_updated: 'رمز به‌روز شد' password_updated: 'رمز به‌روز شد'
password_not_updated_demo: "در حالت نمایشی نمی‌توانید رمز کاربر را عوض کنید." password_not_updated_demo: "در حالت نمایشی نمی‌توانید رمز کاربر را عوض کنید."
user_updated: 'اطلاعات به‌روز شد' user_updated: 'اطلاعات به‌روز شد'
rss_updated: 'اطلاعات آر-اس-اس به‌روز شد' feed_updated: 'اطلاعات آر-اس-اس به‌روز شد'
tagging_rules_updated: 'برچسب‌گذاری خودکار به‌روز شد' tagging_rules_updated: 'برچسب‌گذاری خودکار به‌روز شد'
tagging_rules_deleted: 'قانون برچسب‌گذاری پاک شد' tagging_rules_deleted: 'قانون برچسب‌گذاری پاک شد'
rss_token_updated: 'کد آر-اس-اس به‌روز شد' feed_token_updated: 'کد آر-اس-اس به‌روز شد'
# annotations_reset: Annotations reset # annotations_reset: Annotations reset
# tags_reset: Tags reset # tags_reset: Tags reset
# entries_reset: Entries reset # entries_reset: Entries reset
@ -588,9 +610,11 @@ flashes:
entry_starred: 'مقاله برگزیده شد' entry_starred: 'مقاله برگزیده شد'
entry_unstarred: 'مقاله نابرگزیده شد' entry_unstarred: 'مقاله نابرگزیده شد'
entry_deleted: 'مقاله پاک شد' entry_deleted: 'مقاله پاک شد'
# no_random_entry: 'No article with these criterias was found'
tag: tag:
notice: notice:
tag_added: 'برچسب افزوده شد' tag_added: 'برچسب افزوده شد'
# tag_renamed: 'Tag renamed'
import: import:
notice: notice:
failed: 'درون‌ریزی شکست خورد. لطفاً دوباره تلاش کنید.' failed: 'درون‌ریزی شکست خورد. لطفاً دوباره تلاش کنید.'

View file

@ -37,6 +37,7 @@ menu:
add_new_entry: "Sauvegarder un nouvel article" add_new_entry: "Sauvegarder un nouvel article"
search: "Rechercher" search: "Rechercher"
filter_entries: "Filtrer les articles" filter_entries: "Filtrer les articles"
random_entry: Aller à un article aléatoire de cette liste
export: "Exporter" export: "Exporter"
search_form: search_form:
input_label: "Saisissez votre terme de recherche" input_label: "Saisissez votre terme de recherche"
@ -53,11 +54,12 @@ config:
page_title: "Configuration" page_title: "Configuration"
tab_menu: tab_menu:
settings: "Paramètres" settings: "Paramètres"
rss: "RSS" feed: "Flux"
user_info: "Mon compte" user_info: "Mon compte"
password: "Mot de passe" password: "Mot de passe"
rules: "Règles de tag automatiques" rules: "Règles de tag automatiques"
new_user: "Créer un compte" new_user: "Créer un compte"
reset: "Réinitialisation"
form: form:
save: "Enregistrer" save: "Enregistrer"
form_settings: form_settings:
@ -83,25 +85,33 @@ config:
help_reading_speed: "wallabag calcule une durée de lecture pour chaque article. Vous pouvez définir ici, grâce à cette liste déroulante, si vous lisez plus ou moins vite. wallabag recalculera la durée de lecture de chaque article." help_reading_speed: "wallabag calcule une durée de lecture pour chaque article. Vous pouvez définir ici, grâce à cette liste déroulante, si vous lisez plus ou moins vite. wallabag recalculera la durée de lecture de chaque article."
help_language: "Vous pouvez définir la langue de linterface de wallabag." help_language: "Vous pouvez définir la langue de linterface de wallabag."
help_pocket_consumer_key: "Nécessaire pour limport depuis Pocket. Vous pouvez le créer depuis votre compte Pocket." help_pocket_consumer_key: "Nécessaire pour limport depuis Pocket. Vous pouvez le créer depuis votre compte Pocket."
form_rss: form_feed:
description: "Les flux RSS fournis par wallabag vous permettent de lire vos articles sauvegardés dans votre lecteur de flux préféré. Pour pouvoir les utiliser, vous devez dabord créer un jeton." description: "Les flux Atom fournis par wallabag vous permettent de lire vos articles sauvegardés dans votre lecteur de flux préféré. Pour pouvoir les utiliser, vous devez dabord créer un jeton."
token_label: "Jeton RSS" token_label: "Jeton de flux"
no_token: "Aucun jeton généré" no_token: "Aucun jeton généré"
token_create: "Créez votre jeton" token_create: "Créez votre jeton"
token_reset: "Réinitialisez votre jeton" token_reset: "Réinitialisez votre jeton"
rss_links: "Adresses de vos flux RSS" feed_links: "Adresses de vos flux"
rss_link: feed_link:
unread: "Non lus" unread: "Non lus"
starred: "Favoris" starred: "Favoris"
archive: "Lus" archive: "Lus"
all: "Tous" all: "Tous"
rss_limit: "Nombre darticles dans le flux" feed_limit: "Nombre darticles dans le flux"
form_user: form_user:
two_factor_description: "Activer lauthentification double-facteur veut dire que vous allez recevoir un code par courriel à chaque nouvelle connexion non approuvée." two_factor_description: "Activer lauthentification double-facteur veut dire que vous allez recevoir un code par courriel OU que vous devriez utiliser une application de mot de passe à usage unique (comme Google Authenticator, Authy or FreeOTP) pour obtenir un code temporaire à chaque nouvelle connexion non approuvée. Vous ne pouvez pas choisir les deux options."
name_label: "Nom" name_label: "Nom"
email_label: "Adresse courriel" email_label: "Adresse courriel"
twoFactorAuthentication_label: "Double authentification" two_factor:
help_twoFactorAuthentication: "Si vous activez 2FA, à chaque tentative de connexion à wallabag, vous recevrez un code par email." emailTwoFactor_label: 'En utlisant lemail (recevez un code par email)'
googleTwoFactor_label: 'En utilisant une application de mot de passe à usage unique (ouvrez lapp, comme Google Authenticator, Authy or FreeOTP, pour obtenir un mot de passe à usage unique)'
table_method: Méthode
table_state: État
table_action: Action
state_enabled: Activé
state_disabled: Désactivé
action_email: Utiliser l'email
action_app: Utiliser une app OTP
delete: delete:
title: "Supprimer mon compte (attention danger !)" title: "Supprimer mon compte (attention danger !)"
description: "Si vous confirmez la suppression de votre compte, TOUS les articles, TOUS les tags, TOUTES les annotations et votre compte seront DÉFINITIVEMENT supprimé (cest IRRÉVERSIBLE). Vous serez ensuite déconnecté." description: "Si vous confirmez la suppression de votre compte, TOUS les articles, TOUS les tags, TOUTES les annotations et votre compte seront DÉFINITIVEMENT supprimé (cest IRRÉVERSIBLE). Vous serez ensuite déconnecté."
@ -159,6 +169,15 @@ config:
and: "Une règle ET lautre" and: "Une règle ET lautre"
matches: "Teste si un <i>sujet</i> correspond à une <i>recherche</i> (non sensible à la casse).<br />Exemple : <code>title matches \"football\"</code>" matches: "Teste si un <i>sujet</i> correspond à une <i>recherche</i> (non sensible à la casse).<br />Exemple : <code>title matches \"football\"</code>"
notmatches: "Teste si un <i>sujet</i> ne correspond pas à une <i>recherche</i> (non sensible à la casse).<br />Exemple : <code>title notmatches \"football\"</code>" notmatches: "Teste si un <i>sujet</i> ne correspond pas à une <i>recherche</i> (non sensible à la casse).<br />Exemple : <code>title notmatches \"football\"</code>"
otp:
page_title: Authentification double-facteur
app:
two_factor_code_description_1: Vous venez dactiver lauthentification double-facteur, ouvrez votre application OTP pour configurer la génération du mot de passe à usage unique. Ces informations disparaîtront après un rechargement de la page.
two_factor_code_description_2: 'Vous pouvez scanner le QR code avec votre application :'
two_factor_code_description_3: 'Noubliez pas de sauvegarder ces codes de secours dans un endroit sûr, vous pourrez les utiliser si vous ne pouvez plus accéder à votre application OTP :'
two_factor_code_description_4: 'Testez un code généré par votre application OTP :'
cancel: Annuler
enable: Activer
entry: entry:
default_title: "Titre de larticle" default_title: "Titre de larticle"
@ -353,7 +372,7 @@ quickstart:
title: "Configurez lapplication" title: "Configurez lapplication"
description: "Pour voir une application qui vous correspond, allez voir du côté de la configuration de wallabag." description: "Pour voir une application qui vous correspond, allez voir du côté de la configuration de wallabag."
language: "Changez la langue et le design de lapplication" language: "Changez la langue et le design de lapplication"
rss: "Activez les flux RSS" feed: "Activez les flux Atom"
tagging_rules: "Écrivez des règles pour classer automatiquement vos articles" tagging_rules: "Écrivez des règles pour classer automatiquement vos articles"
admin: admin:
title: "Administration" title: "Administration"
@ -404,6 +423,8 @@ tag:
new: new:
add: "Ajouter" add: "Ajouter"
placeholder: "Vous pouvez ajouter plusieurs tags, séparés par une virgule." placeholder: "Vous pouvez ajouter plusieurs tags, séparés par une virgule."
rename:
placeholder: 'Vous pouvez changer le nom de votre tag.'
export: 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>' 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>'
@ -530,6 +551,8 @@ user:
enabled_label: "Activé" enabled_label: "Activé"
last_login_label: "Dernière connexion" last_login_label: "Dernière connexion"
twofactor_label: "Double authentification" twofactor_label: "Double authentification"
twofactor_email_label: Double authentification par email
twofactor_google_label: Double authentification par OTP app
save: "Sauvegarder" save: "Sauvegarder"
delete: "Supprimer" delete: "Supprimer"
delete_confirm: "Êtes-vous sûr ?" delete_confirm: "Êtes-vous sûr ?"
@ -567,14 +590,15 @@ flashes:
password_updated: "Votre mot de passe a bien été mis à jour" password_updated: "Votre mot de passe a bien été mis à jour"
password_not_updated_demo: "En démo, vous ne pouvez pas changer le mot de passe de cet utilisateur." password_not_updated_demo: "En démo, vous ne pouvez pas changer le mot de passe de cet utilisateur."
user_updated: "Vos informations personnelles ont bien été mises à jour" user_updated: "Vos informations personnelles ont bien été mises à jour"
rss_updated: "La configuration des flux RSS a bien été mise à jour" feed_updated: "La configuration des flux a bien été mise à jour"
tagging_rules_updated: "Règles mises à jour" tagging_rules_updated: "Règles mises à jour"
tagging_rules_deleted: "Règle supprimée" tagging_rules_deleted: "Règle supprimée"
rss_token_updated: "Jeton RSS mis à jour" feed_token_updated: "Jeton des flux mis à jour"
annotations_reset: "Annotations supprimées" annotations_reset: "Annotations supprimées"
tags_reset: "Tags supprimés" tags_reset: "Tags supprimés"
entries_reset: "Articles supprimés" entries_reset: "Articles supprimés"
archived_reset: "Articles archivés supprimés" archived_reset: "Articles archivés supprimés"
otp_enabled: "Authentification à double-facteur activée"
entry: entry:
notice: notice:
entry_already_saved: "Article déjà sauvegardé le %date%" entry_already_saved: "Article déjà sauvegardé le %date%"
@ -588,9 +612,11 @@ flashes:
entry_starred: "Article ajouté dans les favoris" entry_starred: "Article ajouté dans les favoris"
entry_unstarred: "Article retiré des favoris" entry_unstarred: "Article retiré des favoris"
entry_deleted: "Article supprimé" entry_deleted: "Article supprimé"
no_random_entry: "Aucun article correspond aux critères n'a été trouvé"
tag: tag:
notice: notice:
tag_added: "Tag ajouté" tag_added: "Tag ajouté"
tag_renamed: "Tag renommé"
import: import:
notice: notice:
failed: "Limport a échoué, veuillez ré-essayer" failed: "Limport a échoué, veuillez ré-essayer"

View file

@ -37,6 +37,7 @@ menu:
add_new_entry: 'Aggiungi un nuovo contenuto' add_new_entry: 'Aggiungi un nuovo contenuto'
search: 'Cerca' search: 'Cerca'
filter_entries: 'Filtra contenuti' filter_entries: 'Filtra contenuti'
# random_entry: Jump to a random entry from that list
export: 'Esporta' export: 'Esporta'
search_form: search_form:
input_label: 'Inserisci qui la tua ricerca' input_label: 'Inserisci qui la tua ricerca'
@ -53,11 +54,12 @@ config:
page_title: 'Configurazione' page_title: 'Configurazione'
tab_menu: tab_menu:
settings: 'Impostazioni' settings: 'Impostazioni'
rss: 'RSS' feed: 'RSS'
user_info: 'Informazioni utente' user_info: 'Informazioni utente'
password: 'Password' password: 'Password'
rules: 'Regole di etichettatura' rules: 'Regole di etichettatura'
new_user: 'Aggiungi utente' new_user: 'Aggiungi utente'
reset: 'Area di reset'
form: form:
save: 'Salva' save: 'Salva'
form_settings: form_settings:
@ -83,25 +85,32 @@ config:
help_reading_speed: "wallabag calcola un tempo di lettura per ogni articolo. Puoi definire qui, grazie a questa lista, se sei un lettore lento o veloce. wallabag ricalcolerà la velocità di lettura per ogni articolo." help_reading_speed: "wallabag calcola un tempo di lettura per ogni articolo. Puoi definire qui, grazie a questa lista, se sei un lettore lento o veloce. wallabag ricalcolerà la velocità di lettura per ogni articolo."
help_language: "Puoi cambiare la lingua dell'interfaccia di wallabag." help_language: "Puoi cambiare la lingua dell'interfaccia di wallabag."
help_pocket_consumer_key: "Richiesta per importare da Pocket. La puoi creare nel tuo account Pocket." help_pocket_consumer_key: "Richiesta per importare da Pocket. La puoi creare nel tuo account Pocket."
form_rss: form_feed:
description: 'I feed RSS generati da wallabag ti permettono di leggere i tuoi contenuti salvati con il tuo lettore di RSS preferito. Prima, devi generare un token.' description: 'I feed RSS generati da wallabag ti permettono di leggere i tuoi contenuti salvati con il tuo lettore di RSS preferito. Prima, devi generare un token.'
token_label: 'Token RSS' token_label: 'Token RSS'
no_token: 'Nessun token' no_token: 'Nessun token'
token_create: 'Crea il tuo token' token_create: 'Crea il tuo token'
token_reset: 'Rigenera il tuo token' token_reset: 'Rigenera il tuo token'
rss_links: 'Collegamenti RSS' feed_links: 'Collegamenti RSS'
rss_link: feed_link:
unread: 'Non letti' unread: 'Non letti'
starred: 'Preferiti' starred: 'Preferiti'
archive: 'Archiviati' archive: 'Archiviati'
# all: 'All' # all: 'All'
rss_limit: 'Numero di elementi nel feed' feed_limit: 'Numero di elementi nel feed'
form_user: form_user:
two_factor_description: "Abilitando l'autenticazione a due fattori riceverai una e-mail con un codice per ogni nuova connesione non verificata" # two_factor_description: "Enabling two factor authentication means you'll receive an email with a code OR need to use an OTP app (like Google Authenticator, Authy or FreeOTP) to get a one time code on every new untrusted connection. You can't choose both option."
name_label: 'Nome' name_label: 'Nome'
email_label: 'E-mail' email_label: 'E-mail'
twoFactorAuthentication_label: 'Autenticazione a due fattori' # emailTwoFactor_label: 'Using email (receive a code by email)'
help_twoFactorAuthentication: "Se abiliti l'autenticazione a due fattori, ogni volta che vorrai connetterti a wallabag, riceverai un codice via E-mail." # googleTwoFactor_label: 'Using an OTP app (open the app, like Google Authenticator, Authy or FreeOTP, to get a one time code)'
# table_method: Method
# table_state: State
# table_action: Action
# state_enabled: Enabled
# state_disabled: Disabled
# action_email: Use email
# action_app: Use OTP App
delete: delete:
title: Cancella il mio account (zona pericolosa) title: Cancella il mio account (zona pericolosa)
description: Rimuovendo il tuo account, TUTTI i tuoi articoli, TUTTE le tue etichette, TUTTE le tue annotazioni ed il tuo account verranno rimossi PERMANENTEMENTE (impossibile da ANNULLARE). Verrai poi disconnesso. description: Rimuovendo il tuo account, TUTTI i tuoi articoli, TUTTE le tue etichette, TUTTE le tue annotazioni ed il tuo account verranno rimossi PERMANENTEMENTE (impossibile da ANNULLARE). Verrai poi disconnesso.
@ -159,6 +168,15 @@ config:
and: "Una regola E un'altra" and: "Una regola E un'altra"
matches: 'Verifica che un <i>oggetto</i> risulti in una <i>ricerca</i> (case-insensitive).<br />Esempio: <code>titolo contiene "football"</code>' matches: 'Verifica che un <i>oggetto</i> risulti in una <i>ricerca</i> (case-insensitive).<br />Esempio: <code>titolo contiene "football"</code>'
# notmatches: 'Tests that a <i>subject</i> doesn''t match match a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>' # notmatches: 'Tests that a <i>subject</i> doesn''t match match a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>'
otp:
# page_title: Two-factor authentication
# app:
# two_factor_code_description_1: You just enabled the OTP two factor authentication, open your OTP app and use that code to get a one time password. It'll disapear after a page reload.
# two_factor_code_description_2: 'You can scan that QR Code with your app:'
# two_factor_code_description_3: 'Also, save these backup codes in a safe place, you can use them in case you lose access to your OTP app:'
# two_factor_code_description_4: 'Test an OTP code from your configured app:'
# cancel: Cancel
# enable: Enable
entry: entry:
default_title: "Titolo del contenuto" default_title: "Titolo del contenuto"
@ -353,7 +371,7 @@ quickstart:
title: "Configura l'applicazione" title: "Configura l'applicazione"
description: "Per avere un'applicazione che ti soddisfi, dai un'occhiata alla configurazione di wallabag." description: "Per avere un'applicazione che ti soddisfi, dai un'occhiata alla configurazione di wallabag."
language: 'Cambia lingua e design' language: 'Cambia lingua e design'
rss: 'Abilita i feed RSS' feed: 'Abilita i feed RSS'
tagging_rules: 'Scrivi delle regole per taggare automaticamente i contenuti' tagging_rules: 'Scrivi delle regole per taggare automaticamente i contenuti'
admin: admin:
title: 'Amministrazione' title: 'Amministrazione'
@ -404,6 +422,8 @@ tag:
new: new:
add: 'Aggiungi' add: 'Aggiungi'
placeholder: 'Puoi aggiungere varie etichette, separate da una virgola.' placeholder: 'Puoi aggiungere varie etichette, separate da una virgola.'
rename:
# placeholder: 'You can update tag name.'
# export: # 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>' # 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>'
@ -529,7 +549,8 @@ user:
email_label: 'E-mail' email_label: 'E-mail'
enabled_label: 'Abilitato' enabled_label: 'Abilitato'
last_login_label: 'Ultima connessione' last_login_label: 'Ultima connessione'
twofactor_label: Autenticazione a due fattori # twofactor_email_label: Two factor authentication by email
# twofactor_google_label: Two factor authentication by OTP app
save: Salva save: Salva
delete: Cancella delete: Cancella
delete_confirm: Sei sicuro? delete_confirm: Sei sicuro?
@ -567,10 +588,10 @@ flashes:
password_updated: 'Password aggiornata' password_updated: 'Password aggiornata'
password_not_updated_demo: "In modalità demo, non puoi cambiare la password dell'utente." password_not_updated_demo: "In modalità demo, non puoi cambiare la password dell'utente."
user_updated: 'Informazioni aggiornate' user_updated: 'Informazioni aggiornate'
rss_updated: 'Informazioni RSS aggiornate' feed_updated: 'Informazioni RSS aggiornate'
tagging_rules_updated: 'Regole di etichettatura aggiornate' tagging_rules_updated: 'Regole di etichettatura aggiornate'
tagging_rules_deleted: 'Regola di etichettatura eliminate' tagging_rules_deleted: 'Regola di etichettatura eliminate'
rss_token_updated: 'RSS token aggiornato' feed_token_updated: 'RSS token aggiornato'
annotations_reset: Reset annotazioni annotations_reset: Reset annotazioni
tags_reset: Reset etichette tags_reset: Reset etichette
entries_reset: Reset articoli entries_reset: Reset articoli
@ -588,9 +609,11 @@ flashes:
entry_starred: 'Contenuto segnato come preferito' entry_starred: 'Contenuto segnato come preferito'
entry_unstarred: 'Contenuto rimosso dai preferiti' entry_unstarred: 'Contenuto rimosso dai preferiti'
entry_deleted: 'Contenuto eliminato' entry_deleted: 'Contenuto eliminato'
# no_random_entry: 'No article with these criterias was found'
tag: tag:
notice: notice:
tag_added: 'Etichetta aggiunta' tag_added: 'Etichetta aggiunta'
# tag_renamed: 'Tag renamed'
import: import:
notice: notice:
failed: 'Importazione fallita, riprova.' failed: 'Importazione fallita, riprova.'

View file

@ -37,6 +37,7 @@ menu:
add_new_entry: 'Enregistrar un novèl article' add_new_entry: 'Enregistrar un novèl article'
search: 'Cercar' search: 'Cercar'
filter_entries: 'Filtrar los articles' filter_entries: 'Filtrar los articles'
# random_entry: Jump to a random entry from that list
export: 'Exportar' export: 'Exportar'
search_form: search_form:
input_label: 'Picatz vòstre mot-clau a cercar aquí' input_label: 'Picatz vòstre mot-clau a cercar aquí'
@ -53,11 +54,12 @@ config:
page_title: 'Configuracion' page_title: 'Configuracion'
tab_menu: tab_menu:
settings: 'Paramètres' settings: 'Paramètres'
rss: 'RSS' feed: 'RSS'
user_info: 'Mon compte' user_info: 'Mon compte'
password: 'Senhal' password: 'Senhal'
rules: "Règlas d'etiquetas automaticas" rules: "Règlas d'etiquetas automaticas"
new_user: 'Crear un compte' new_user: 'Crear un compte'
reset: 'Zòna de reïnicializacion'
form: form:
save: 'Enregistrar' save: 'Enregistrar'
form_settings: form_settings:
@ -83,25 +85,32 @@ config:
help_reading_speed: "wallabag calcula lo temps de lectura per cada article. Podètz lo definir aquí, gràcias a aquesta lista, se sètz un legeire rapid o lent. wallabag tornarà calcular lo temps de lectura per cada article." help_reading_speed: "wallabag calcula lo temps de lectura per cada article. Podètz lo definir aquí, gràcias a aquesta lista, se sètz un legeire rapid o lent. wallabag tornarà calcular lo temps de lectura per cada article."
help_language: "Podètz cambiar la lenga de l'interfàcia de wallabag." help_language: "Podètz cambiar la lenga de l'interfàcia de wallabag."
help_pocket_consumer_key: "Requesida per l'importacion de Pocket. Podètz la crear dins vòstre compte Pocket." help_pocket_consumer_key: "Requesida per l'importacion de Pocket. Podètz la crear dins vòstre compte Pocket."
form_rss: form_feed:
description: "Los fluxes RSS fornits per wallabag vos permeton de legir vòstres articles salvagardats dins vòstre lector de fluxes preferit. Per los poder emplegar, vos cal, d'en primièr crear un geton." description: "Los fluxes RSS fornits per wallabag vos permeton de legir vòstres articles salvagardats dins vòstre lector de fluxes preferit. Per los poder emplegar, vos cal, d'en primièr crear un geton."
token_label: 'Geton RSS' token_label: 'Geton RSS'
no_token: 'Pas cap de geton generat' no_token: 'Pas cap de geton generat'
token_create: 'Creatz vòstre geton' token_create: 'Creatz vòstre geton'
token_reset: 'Reïnicializatz vòstre geton' token_reset: 'Reïnicializatz vòstre geton'
rss_links: 'URLs de vòstres fluxes RSS' feed_links: 'URLs de vòstres fluxes RSS'
rss_link: feed_link:
unread: 'Pas legits' unread: 'Pas legits'
starred: 'Favorits' starred: 'Favorits'
archive: 'Legits' archive: 'Legits'
all: 'Totes' all: 'Totes'
rss_limit: "Nombre d'articles dins un flux RSS" feed_limit: "Nombre d'articles dins un flux"
form_user: form_user:
two_factor_description: "Activar l'autentificacion en dos temps vòl dire que recebretz un còdi per corrièl per cada novèla connexion pas aprovada." # two_factor_description: "Enabling two factor authentication means you'll receive an email with a code OR need to use an OTP app (like Google Authenticator, Authy or FreeOTP) to get a one time code on every new untrusted connection. You can't choose both option."
name_label: 'Nom' name_label: 'Nom'
email_label: 'Adreça de corrièl' email_label: 'Adreça de corrièl'
twoFactorAuthentication_label: 'Dobla autentificacion' # emailTwoFactor_label: 'Using email (receive a code by email)'
help_twoFactorAuthentication: "S'avètz activat l'autentificacion en dos temps, cada còp que volètz vos connectar a wallabag, recebretz un còdi per corrièl." # googleTwoFactor_label: 'Using an OTP app (open the app, like Google Authenticator, Authy or FreeOTP, to get a one time code)'
# table_method: Method
# table_state: State
# table_action: Action
# state_enabled: Enabled
# state_disabled: Disabled
# action_email: Use email
# action_app: Use OTP App
delete: delete:
title: Suprimir mon compte (Mèfi zòna perilhosa) title: Suprimir mon compte (Mèfi zòna perilhosa)
description: Se confirmatz la supression de vòstre compte, TOTES vòstres articles, TOTAS vòstras etiquetas, TOTAS vòstras anotacions e vòstre compte seràn suprimits per totjorn. E aquò es IRREVERSIBLE. Puèi seretz desconnectat. description: Se confirmatz la supression de vòstre compte, TOTES vòstres articles, TOTAS vòstras etiquetas, TOTAS vòstras anotacions e vòstre compte seràn suprimits per totjorn. E aquò es IRREVERSIBLE. Puèi seretz desconnectat.
@ -159,6 +168,15 @@ config:
and: "Una règla E l'autra" and: "Una règla E l'autra"
matches: 'Teste se un <i>subjècte</i> correspond a una <i>recèrca</i> (non sensibla a la cassa).<br />Exemple:<code>title matches \"football\"</code>' matches: 'Teste se un <i>subjècte</i> correspond a una <i>recèrca</i> (non sensibla a la cassa).<br />Exemple:<code>title matches \"football\"</code>'
notmatches: 'Teste se <i>subjècte</i> correspond pas a una <i>recèrca</i> (sensibla a la cassa).<br />Example:<code>title notmatches "football"</code>' notmatches: 'Teste se <i>subjècte</i> correspond pas a una <i>recèrca</i> (sensibla a la cassa).<br />Example:<code>title notmatches "football"</code>'
otp:
# page_title: Two-factor authentication
# app:
# two_factor_code_description_1: You just enabled the OTP two factor authentication, open your OTP app and use that code to get a one time password. It'll disapear after a page reload.
# two_factor_code_description_2: 'You can scan that QR Code with your app:'
# two_factor_code_description_3: 'Also, save these backup codes in a safe place, you can use them in case you lose access to your OTP app:'
# two_factor_code_description_4: 'Test an OTP code from your configured app:'
# cancel: Cancel
# enable: Enable
entry: entry:
default_title: "Títol de l'article" default_title: "Títol de l'article"
@ -353,7 +371,7 @@ quickstart:
title: "Configuratz l'aplicacion" title: "Configuratz l'aplicacion"
description: "Per fin d'aver una aplicacion que vos va ben, anatz veire la configuracion de wallabag." description: "Per fin d'aver una aplicacion que vos va ben, anatz veire la configuracion de wallabag."
language: "Cambiatz la lenga e l'estil de l'aplicacion" language: "Cambiatz la lenga e l'estil de l'aplicacion"
rss: 'Activatz los fluxes RSS' feed: 'Activatz los fluxes RSS'
tagging_rules: 'Escrivètz de règlas per classar automaticament vòstres articles' tagging_rules: 'Escrivètz de règlas per classar automaticament vòstres articles'
admin: admin:
title: 'Administracion' title: 'Administracion'
@ -404,6 +422,8 @@ tag:
new: new:
add: 'Ajustar' add: 'Ajustar'
placeholder: "Podètz ajustar mai qu'una etiqueta, separadas per de virgula." placeholder: "Podètz ajustar mai qu'una etiqueta, separadas per de virgula."
rename:
# placeholder: 'You can update tag name.'
export: 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>' 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>'
@ -529,7 +549,8 @@ user:
email_label: 'Adreça de corrièl' email_label: 'Adreça de corrièl'
enabled_label: 'Actiu' enabled_label: 'Actiu'
last_login_label: 'Darrièra connexion' last_login_label: 'Darrièra connexion'
twofactor_label: 'Autentificacion doble-factor' # twofactor_email_label: Two factor authentication by email
# twofactor_google_label: Two factor authentication by OTP app
save: 'Enregistrar' save: 'Enregistrar'
delete: 'Suprimir' delete: 'Suprimir'
delete_confirm: 'Sètz segur?' delete_confirm: 'Sètz segur?'
@ -567,10 +588,10 @@ flashes:
password_updated: 'Vòstre senhal es ben estat mes a jorn' password_updated: 'Vòstre senhal es ben estat mes a jorn'
password_not_updated_demo: "En demostracion, podètz pas cambiar lo senhal d'aqueste utilizaire." password_not_updated_demo: "En demostracion, podètz pas cambiar lo senhal d'aqueste utilizaire."
user_updated: 'Vòstres informacions personnelas son ben estadas mesas a jorn' user_updated: 'Vòstres informacions personnelas son ben estadas mesas a jorn'
rss_updated: 'La configuracion dels fluxes RSS es ben estada mesa a jorn' feed_updated: 'La configuracion dels fluxes RSS es ben estada mesa a jorn'
tagging_rules_updated: 'Règlas misa a jorn' tagging_rules_updated: 'Règlas misa a jorn'
tagging_rules_deleted: 'Règla suprimida' tagging_rules_deleted: 'Règla suprimida'
rss_token_updated: 'Geton RSS mes a jorn' feed_token_updated: 'Geton RSS mes a jorn'
annotations_reset: Anotacions levadas annotations_reset: Anotacions levadas
tags_reset: Etiquetas levadas tags_reset: Etiquetas levadas
entries_reset: Articles levats entries_reset: Articles levats
@ -588,9 +609,11 @@ flashes:
entry_starred: 'Article ajustat dins los favorits' entry_starred: 'Article ajustat dins los favorits'
entry_unstarred: 'Article quitat dels favorits' entry_unstarred: 'Article quitat dels favorits'
entry_deleted: 'Article suprimit' entry_deleted: 'Article suprimit'
# no_random_entry: 'No article with these criterias was found'
tag: tag:
notice: notice:
tag_added: 'Etiqueta ajustada' tag_added: 'Etiqueta ajustada'
# tag_renamed: 'Tag renamed'
import: import:
notice: notice:
failed: "L'importacion a fracassat, mercés de tornar ensajar." failed: "L'importacion a fracassat, mercés de tornar ensajar."

View file

@ -37,6 +37,7 @@ menu:
add_new_entry: 'Dodaj nowy wpis' add_new_entry: 'Dodaj nowy wpis'
search: 'Szukaj' search: 'Szukaj'
filter_entries: 'Filtruj wpisy' filter_entries: 'Filtruj wpisy'
# random_entry: Jump to a random entry from that list
export: 'Eksportuj' export: 'Eksportuj'
search_form: search_form:
input_label: 'Wpisz swoje zapytanie tutaj' input_label: 'Wpisz swoje zapytanie tutaj'
@ -53,11 +54,12 @@ config:
page_title: 'Konfiguracja' page_title: 'Konfiguracja'
tab_menu: tab_menu:
settings: 'Ustawienia' settings: 'Ustawienia'
rss: 'Kanał RSS' feed: 'Kanał RSS'
user_info: 'Informacje o użytkowniku' user_info: 'Informacje o użytkowniku'
password: 'Hasło' password: 'Hasło'
rules: 'Zasady tagowania' rules: 'Zasady tagowania'
new_user: 'Dodaj użytkownika' new_user: 'Dodaj użytkownika'
reset: 'Reset'
form: form:
save: 'Zapisz' save: 'Zapisz'
form_settings: form_settings:
@ -83,25 +85,32 @@ config:
help_reading_speed: "wallabag oblicza czas czytania każdego artykułu. Dzięki tej liście możesz określić swoje tempo. Wallabag przeliczy ponownie czas potrzebny, na przeczytanie każdego z artykułów." help_reading_speed: "wallabag oblicza czas czytania każdego artykułu. Dzięki tej liście możesz określić swoje tempo. Wallabag przeliczy ponownie czas potrzebny, na przeczytanie każdego z artykułów."
help_language: "Możesz zmienić język interfejsu wallabag." help_language: "Możesz zmienić język interfejsu wallabag."
help_pocket_consumer_key: "Wymagane dla importu z Pocket. Możesz go stworzyć na swoim koncie Pocket." help_pocket_consumer_key: "Wymagane dla importu z Pocket. Możesz go stworzyć na swoim koncie Pocket."
form_rss: form_feed:
description: 'Kanały RSS prowadzone przez wallabag pozwalają Ci na czytanie twoich zapisanych artykułów w twoim ulubionym czytniku RSS. Musisz najpierw wynegenerować tokena.' description: 'Kanały RSS prowadzone przez wallabag pozwalają Ci na czytanie twoich zapisanych artykułów w twoim ulubionym czytniku RSS. Musisz najpierw wynegenerować tokena.'
token_label: 'Token RSS' token_label: 'Token RSS'
no_token: 'Brak tokena' no_token: 'Brak tokena'
token_create: 'Stwórz tokena' token_create: 'Stwórz tokena'
token_reset: 'Zresetuj swojego tokena' token_reset: 'Zresetuj swojego tokena'
rss_links: 'RSS links' feed_links: 'RSS links'
rss_link: feed_link:
unread: 'Nieprzeczytane' unread: 'Nieprzeczytane'
starred: 'Oznaczone gwiazdką' starred: 'Oznaczone gwiazdką'
archive: 'Archiwum' archive: 'Archiwum'
all: 'Wszystkie' all: 'Wszystkie'
rss_limit: 'Link do RSS' feed_limit: 'Link do RSS'
form_user: form_user:
two_factor_description: "Włączenie autoryzacji dwuetapowej oznacza, że będziesz otrzymywał maile z kodem przy każdym nowym, niezaufanym połączeniu" # two_factor_description: "Enabling two factor authentication means you'll receive an email with a code OR need to use an OTP app (like Google Authenticator, Authy or FreeOTP) to get a one time code on every new untrusted connection. You can't choose both option."
name_label: 'Nazwa' name_label: 'Nazwa'
email_label: 'Adres email' email_label: 'Adres email'
twoFactorAuthentication_label: 'Autoryzacja dwuetapowa' # emailTwoFactor_label: 'Using email (receive a code by email)'
help_twoFactorAuthentication: "Jeżeli włączysz autoryzację dwuetapową. Za każdym razem, kiedy będziesz chciał się zalogować, dostaniesz kod na swój e-mail." # googleTwoFactor_label: 'Using an OTP app (open the app, like Google Authenticator, Authy or FreeOTP, to get a one time code)'
# table_method: Method
# table_state: State
# table_action: Action
# state_enabled: Enabled
# state_disabled: Disabled
# action_email: Use email
# action_app: Use OTP App
delete: delete:
title: Usuń moje konto (niebezpieczna strefa !) title: Usuń moje konto (niebezpieczna strefa !)
description: Jeżeli usuniesz swoje konto, wszystkie twoje artykuły, tagi, adnotacje, oraz konto zostaną trwale usunięte (operacja jest NIEODWRACALNA). Następnie zostaniesz wylogowany. description: Jeżeli usuniesz swoje konto, wszystkie twoje artykuły, tagi, adnotacje, oraz konto zostaną trwale usunięte (operacja jest NIEODWRACALNA). Następnie zostaniesz wylogowany.
@ -159,6 +168,15 @@ config:
and: 'Jedna reguła I inna' and: 'Jedna reguła I inna'
matches: 'Sprawdź czy <i>temat</i> pasuje <i>szukaj</i> (duże lub małe litery).<br />Przykład: <code>tytuł zawiera "piłka nożna"</code>' matches: 'Sprawdź czy <i>temat</i> pasuje <i>szukaj</i> (duże lub małe litery).<br />Przykład: <code>tytuł zawiera "piłka nożna"</code>'
notmatches: 'Sprawdź czy <i>temat</i> nie zawiera <i>szukaj</i> (duże lub małe litery).<br />Przykład: <code>tytuł nie zawiera "piłka nożna"</code>' notmatches: 'Sprawdź czy <i>temat</i> nie zawiera <i>szukaj</i> (duże lub małe litery).<br />Przykład: <code>tytuł nie zawiera "piłka nożna"</code>'
otp:
# page_title: Two-factor authentication
# app:
# two_factor_code_description_1: You just enabled the OTP two factor authentication, open your OTP app and use that code to get a one time password. It'll disapear after a page reload.
# two_factor_code_description_2: 'You can scan that QR Code with your app:'
# two_factor_code_description_3: 'Also, save these backup codes in a safe place, you can use them in case you lose access to your OTP app:'
# two_factor_code_description_4: 'Test an OTP code from your configured app:'
# cancel: Cancel
# enable: Enable
entry: entry:
default_title: 'Tytuł wpisu' default_title: 'Tytuł wpisu'
@ -353,7 +371,7 @@ quickstart:
title: 'Konfiguruj aplikację' title: 'Konfiguruj aplikację'
description: 'W celu dopasowania aplikacji do swoich upodobań, zobacz konfigurację aplikacji' description: 'W celu dopasowania aplikacji do swoich upodobań, zobacz konfigurację aplikacji'
language: 'Zmień język i wygląd' language: 'Zmień język i wygląd'
rss: 'Włącz kanały RSS' feed: 'Włącz kanały RSS'
tagging_rules: 'Napisz reguły pozwalające na automatyczne otagowanie twoich artykułów' tagging_rules: 'Napisz reguły pozwalające na automatyczne otagowanie twoich artykułów'
admin: admin:
title: 'Administracja' title: 'Administracja'
@ -404,6 +422,8 @@ tag:
new: new:
add: 'Dodaj' add: 'Dodaj'
placeholder: 'Możesz dodać kilka tagów, oddzielając je przecinkami.' placeholder: 'Możesz dodać kilka tagów, oddzielając je przecinkami.'
rename:
placeholder: 'Możesz zaktualizować nazwę taga.'
export: 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>'
@ -529,7 +549,8 @@ user:
email_label: 'Adres email' email_label: 'Adres email'
enabled_label: 'Włączony' enabled_label: 'Włączony'
last_login_label: 'Ostatnie logowanie' last_login_label: 'Ostatnie logowanie'
twofactor_label: Autoryzacja dwuetapowa # twofactor_email_label: Two factor authentication by email
# twofactor_google_label: Two factor authentication by OTP app
save: Zapisz save: Zapisz
delete: Usuń delete: Usuń
delete_confirm: Jesteś pewien? delete_confirm: Jesteś pewien?
@ -567,10 +588,10 @@ flashes:
password_updated: 'Hasło zaktualizowane' password_updated: 'Hasło zaktualizowane'
password_not_updated_demo: "In demonstration mode, you can't change password for this user." password_not_updated_demo: "In demonstration mode, you can't change password for this user."
user_updated: 'Informacje zaktualizowane' user_updated: 'Informacje zaktualizowane'
rss_updated: 'Informacje RSS zaktualizowane' feed_updated: 'Informacje RSS zaktualizowane'
tagging_rules_updated: 'Reguły tagowania zaktualizowane' tagging_rules_updated: 'Reguły tagowania zaktualizowane'
tagging_rules_deleted: 'Reguła tagowania usunięta' tagging_rules_deleted: 'Reguła tagowania usunięta'
rss_token_updated: 'Token kanału RSS zaktualizowany' feed_token_updated: 'Token kanału RSS zaktualizowany'
annotations_reset: Zresetuj adnotacje annotations_reset: Zresetuj adnotacje
tags_reset: Zresetuj tagi tags_reset: Zresetuj tagi
entries_reset: Zresetuj wpisy entries_reset: Zresetuj wpisy
@ -588,9 +609,11 @@ flashes:
entry_starred: 'Wpis oznaczony gwiazdką' entry_starred: 'Wpis oznaczony gwiazdką'
entry_unstarred: 'Wpis odznaczony gwiazdką' entry_unstarred: 'Wpis odznaczony gwiazdką'
entry_deleted: 'Wpis usunięty' entry_deleted: 'Wpis usunięty'
# no_random_entry: 'No article with these criterias was found'
tag: tag:
notice: notice:
tag_added: 'Tag dodany' tag_added: 'Tag dodany'
tag_renamed: 'Nazwa taga zmieniona'
import: import:
notice: notice:
failed: 'Nieudany import, prosimy spróbować ponownie.' failed: 'Nieudany import, prosimy spróbować ponownie.'

View file

@ -37,6 +37,7 @@ menu:
add_new_entry: 'Adicionar uma nova entrada' add_new_entry: 'Adicionar uma nova entrada'
search: 'Pesquisa' search: 'Pesquisa'
filter_entries: 'Filtrar entradas' filter_entries: 'Filtrar entradas'
# random_entry: Jump to a random entry from that list
export: 'Exportar' export: 'Exportar'
search_form: search_form:
input_label: 'Digite aqui sua pesquisa' input_label: 'Digite aqui sua pesquisa'
@ -53,11 +54,12 @@ config:
page_title: 'Config' page_title: 'Config'
tab_menu: tab_menu:
settings: 'Configurações' settings: 'Configurações'
rss: 'RSS' feed: 'RSS'
user_info: 'Informação do Usuário' user_info: 'Informação do Usuário'
password: 'Senha' password: 'Senha'
rules: 'Regras de tags' rules: 'Regras de tags'
new_user: 'Adicionar um usuário' new_user: 'Adicionar um usuário'
# reset: 'Reset area'
form: form:
save: 'Salvar' save: 'Salvar'
form_settings: form_settings:
@ -83,25 +85,32 @@ config:
# help_reading_speed: "wallabag calculates a reading time for each article. You can define here, thanks to this list, if you are a fast or a slow reader. wallabag will recalculate the reading time for each article." # help_reading_speed: "wallabag calculates a reading time for each article. You can define here, thanks to this list, if you are a fast or a slow reader. wallabag will recalculate the reading time for each article."
# help_language: "You can change the language of wallabag interface." # help_language: "You can change the language of wallabag interface."
# help_pocket_consumer_key: "Required for Pocket import. You can create it in your Pocket account." # help_pocket_consumer_key: "Required for Pocket import. You can create it in your Pocket account."
form_rss: form_feed:
description: 'Feeds RSS providos pelo wallabag permitem que você leia seus artigos salvos em seu leitor de RSS favorito. Você precisa gerar um token primeiro.' description: 'Feeds RSS providos pelo wallabag permitem que você leia seus artigos salvos em seu leitor de RSS favorito. Você precisa gerar um token primeiro.'
token_label: 'Token RSS' token_label: 'Token RSS'
no_token: 'Nenhum Token' no_token: 'Nenhum Token'
token_create: 'Criar seu token' token_create: 'Criar seu token'
token_reset: 'Gerar novamente seu token' token_reset: 'Gerar novamente seu token'
rss_links: 'Links RSS' feed_links: 'Links RSS'
rss_link: feed_link:
unread: 'Não lido' unread: 'Não lido'
starred: 'Destacado' starred: 'Destacado'
archive: 'Arquivado' archive: 'Arquivado'
# all: 'All' # all: 'All'
rss_limit: 'Número de itens no feed' feed_limit: 'Número de itens no feed'
form_user: form_user:
two_factor_description: 'Habilitar autenticação de dois passos significa que você receberá um e-mail com um código a cada nova conexão desconhecida.' # two_factor_description: "Enabling two factor authentication means you'll receive an email with a code OR need to use an OTP app (like Google Authenticator, Authy or FreeOTP) to get a one time code on every new untrusted connection. You can't choose both option."
name_label: 'Nome' name_label: 'Nome'
email_label: 'E-mail' email_label: 'E-mail'
twoFactorAuthentication_label: 'Autenticação de dois passos' # emailTwoFactor_label: 'Using email (receive a code by email)'
# help_twoFactorAuthentication: "If you enable 2FA, each time you want to login to wallabag, you'll receive a code by email." # googleTwoFactor_label: 'Using an OTP app (open the app, like Google Authenticator, Authy or FreeOTP, to get a one time code)'
# table_method: Method
# table_state: State
# table_action: Action
# state_enabled: Enabled
# state_disabled: Disabled
# action_email: Use email
# action_app: Use OTP App
delete: delete:
# title: Delete my account (a.k.a danger zone) # title: Delete my account (a.k.a danger zone)
# description: If you remove your account, ALL your articles, ALL your tags, ALL your annotations and your account will be PERMANENTLY removed (it can't be UNDONE). You'll then be logged out. # description: If you remove your account, ALL your articles, ALL your tags, ALL your annotations and your account will be PERMANENTLY removed (it can't be UNDONE). You'll then be logged out.
@ -159,6 +168,15 @@ config:
and: 'Uma regra E outra' and: 'Uma regra E outra'
matches: 'Testa que um <i>assunto</i> corresponde a uma <i>pesquisa</i> (maiúscula ou minúscula).<br />Exemplo: <code>título corresponde a "futebol"</code>' matches: 'Testa que um <i>assunto</i> corresponde a uma <i>pesquisa</i> (maiúscula ou minúscula).<br />Exemplo: <code>título corresponde a "futebol"</code>'
# notmatches: 'Tests that a <i>subject</i> doesn''t match match a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>' # notmatches: 'Tests that a <i>subject</i> doesn''t match match a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>'
otp:
# page_title: Two-factor authentication
# app:
# two_factor_code_description_1: You just enabled the OTP two factor authentication, open your OTP app and use that code to get a one time password. It'll disapear after a page reload.
# two_factor_code_description_2: 'You can scan that QR Code with your app:'
# two_factor_code_description_3: 'Also, save these backup codes in a safe place, you can use them in case you lose access to your OTP app:'
# two_factor_code_description_4: 'Test an OTP code from your configured app:'
# cancel: Cancel
# enable: Enable
entry: entry:
default_title: 'Título da entrada' default_title: 'Título da entrada'
@ -353,7 +371,7 @@ quickstart:
title: 'Configurar a aplicação' title: 'Configurar a aplicação'
description: 'Para ter uma aplicação que atende você, dê uma olhada na configuração do wallabag.' description: 'Para ter uma aplicação que atende você, dê uma olhada na configuração do wallabag.'
language: 'Alterar idioma e design' language: 'Alterar idioma e design'
rss: 'Habilitar feeds RSS' feed: 'Habilitar feeds RSS'
tagging_rules: 'Escrever regras para acrescentar tags automaticamente em seus artigos' tagging_rules: 'Escrever regras para acrescentar tags automaticamente em seus artigos'
admin: admin:
title: 'Administração' title: 'Administração'
@ -404,6 +422,8 @@ tag:
new: new:
# add: 'Add' # add: 'Add'
# placeholder: 'You can add several tags, separated by a comma.' # placeholder: 'You can add several tags, separated by a comma.'
rename:
# placeholder: 'You can update tag name.'
# export: # 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>' # 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>'
@ -529,7 +549,8 @@ user:
email_label: 'E-mail' email_label: 'E-mail'
enabled_label: 'Habilitado' enabled_label: 'Habilitado'
last_login_label: 'Último login' last_login_label: 'Último login'
twofactor_label: 'Autenticação de dois passos' # twofactor_email_label: Two factor authentication by email
# twofactor_google_label: Two factor authentication by OTP app
save: 'Salvar' save: 'Salvar'
delete: 'Apagar' delete: 'Apagar'
delete_confirm: 'Tem certeza?' delete_confirm: 'Tem certeza?'
@ -567,10 +588,10 @@ flashes:
password_updated: 'Senha atualizada' password_updated: 'Senha atualizada'
password_not_updated_demo: 'Em modo de demonstração, você não pode alterar a senha deste usuário.' password_not_updated_demo: 'Em modo de demonstração, você não pode alterar a senha deste usuário.'
# user_updated: 'Information updated' # user_updated: 'Information updated'
rss_updated: 'Informação de RSS atualizada' feed_updated: 'Informação de RSS atualizada'
tagging_rules_updated: 'Regras de tags atualizadas' tagging_rules_updated: 'Regras de tags atualizadas'
tagging_rules_deleted: 'Regra de tag apagada' tagging_rules_deleted: 'Regra de tag apagada'
rss_token_updated: 'Token RSS atualizado' feed_token_updated: 'Token RSS atualizado'
# annotations_reset: Annotations reset # annotations_reset: Annotations reset
# tags_reset: Tags reset # tags_reset: Tags reset
# entries_reset: Entries reset # entries_reset: Entries reset
@ -588,9 +609,11 @@ flashes:
entry_starred: 'Entrada destacada' entry_starred: 'Entrada destacada'
entry_unstarred: 'Entrada não destacada' entry_unstarred: 'Entrada não destacada'
entry_deleted: 'Entrada apagada' entry_deleted: 'Entrada apagada'
# no_random_entry: 'No article with these criterias was found'
tag: tag:
notice: notice:
tag_added: 'Tag adicionada' tag_added: 'Tag adicionada'
# tag_renamed: 'Tag renamed'
import: import:
notice: notice:
failed: 'Importação falhou, por favor tente novamente.' failed: 'Importação falhou, por favor tente novamente.'

View file

@ -37,6 +37,7 @@ menu:
add_new_entry: 'Introdu un nou articol' add_new_entry: 'Introdu un nou articol'
search: 'Căutare' search: 'Căutare'
filter_entries: 'Filtrează articolele' filter_entries: 'Filtrează articolele'
# random_entry: Jump to a random entry from that list
# export: 'Export' # export: 'Export'
search_form: search_form:
input_label: 'Introdu căutarea ta' input_label: 'Introdu căutarea ta'
@ -53,11 +54,12 @@ config:
page_title: 'Configurație' page_title: 'Configurație'
tab_menu: tab_menu:
settings: 'Setări' settings: 'Setări'
rss: 'RSS' feed: 'RSS'
user_info: 'Informații despre utilizator' user_info: 'Informații despre utilizator'
password: 'Parolă' password: 'Parolă'
# rules: 'Tagging rules' # rules: 'Tagging rules'
new_user: 'Crează un utilizator' new_user: 'Crează un utilizator'
# reset: 'Reset area'
form: form:
save: 'Salvează' save: 'Salvează'
form_settings: form_settings:
@ -83,25 +85,32 @@ config:
# help_reading_speed: "wallabag calculates a reading time for each article. You can define here, thanks to this list, if you are a fast or a slow reader. wallabag will recalculate the reading time for each article." # help_reading_speed: "wallabag calculates a reading time for each article. You can define here, thanks to this list, if you are a fast or a slow reader. wallabag will recalculate the reading time for each article."
# help_language: "You can change the language of wallabag interface." # help_language: "You can change the language of wallabag interface."
# help_pocket_consumer_key: "Required for Pocket import. You can create it in your Pocket account." # help_pocket_consumer_key: "Required for Pocket import. You can create it in your Pocket account."
form_rss: form_feed:
description: 'Feed-urile RSS oferite de wallabag îți permit să-ți citești articolele salvate în reader-ul tău preferat RSS.' description: 'Feed-urile RSS oferite de wallabag îți permit să-ți citești articolele salvate în reader-ul tău preferat RSS.'
token_label: 'RSS-Token' token_label: 'RSS-Token'
no_token: 'Fără token' no_token: 'Fără token'
token_create: 'Crează-ți token' token_create: 'Crează-ți token'
token_reset: 'Resetează-ți token-ul' token_reset: 'Resetează-ți token-ul'
rss_links: 'Link-uri RSS' feed_links: 'Link-uri RSS'
rss_link: feed_link:
unread: 'Unread' unread: 'Unread'
starred: 'Starred' starred: 'Starred'
archive: 'Archived' archive: 'Archived'
# all: 'All' # all: 'All'
rss_limit: 'Limită RSS' feed_limit: 'Limită RSS'
form_user: form_user:
# two_factor_description: "Enabling two factor authentication means you'll receive an email with a code on every new untrusted connexion" # two_factor_description: "Enabling two factor authentication means you'll receive an email with a code OR need to use an OTP app (like Google Authenticator, Authy or FreeOTP) to get a one time code on every new untrusted connection. You can't choose both option."
name_label: 'Nume' name_label: 'Nume'
email_label: 'E-mail' email_label: 'E-mail'
# twoFactorAuthentication_label: 'Two factor authentication' # emailTwoFactor_label: 'Using email (receive a code by email)'
# help_twoFactorAuthentication: "If you enable 2FA, each time you want to login to wallabag, you'll receive a code by email." # googleTwoFactor_label: 'Using an OTP app (open the app, like Google Authenticator, Authy or FreeOTP, to get a one time code)'
# table_method: Method
# table_state: State
# table_action: Action
# state_enabled: Enabled
# state_disabled: Disabled
# action_email: Use email
# action_app: Use OTP App
delete: delete:
# title: Delete my account (a.k.a danger zone) # title: Delete my account (a.k.a danger zone)
# description: If you remove your account, ALL your articles, ALL your tags, ALL your annotations and your account will be PERMANENTLY removed (it can't be UNDONE). You'll then be logged out. # description: If you remove your account, ALL your articles, ALL your tags, ALL your annotations and your account will be PERMANENTLY removed (it can't be UNDONE). You'll then be logged out.
@ -159,6 +168,15 @@ config:
# and: 'One rule AND another' # and: 'One rule AND another'
# matches: 'Tests that a <i>subject</i> matches a <i>search</i> (case-insensitive).<br />Example: <code>title matches "football"</code>' # matches: 'Tests that a <i>subject</i> matches a <i>search</i> (case-insensitive).<br />Example: <code>title matches "football"</code>'
# notmatches: 'Tests that a <i>subject</i> doesn''t match match a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>' # notmatches: 'Tests that a <i>subject</i> doesn''t match match a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>'
otp:
# page_title: Two-factor authentication
# app:
# two_factor_code_description_1: You just enabled the OTP two factor authentication, open your OTP app and use that code to get a one time password. It'll disapear after a page reload.
# two_factor_code_description_2: 'You can scan that QR Code with your app:'
# two_factor_code_description_3: 'Also, save these backup codes in a safe place, you can use them in case you lose access to your OTP app:'
# two_factor_code_description_4: 'Test an OTP code from your configured app:'
# cancel: Cancel
# enable: Enable
entry: entry:
# default_title: 'Title of the entry' # default_title: 'Title of the entry'
@ -353,7 +371,7 @@ quickstart:
# title: 'Configure the application' # title: 'Configure the application'
# description: 'In order to have an application which suits you, have a look into the configuration of wallabag.' # description: 'In order to have an application which suits you, have a look into the configuration of wallabag.'
# language: 'Change language and design' # language: 'Change language and design'
# rss: 'Enable RSS feeds' # feed: 'Enable RSS feeds'
# tagging_rules: 'Write rules to automatically tag your articles' # tagging_rules: 'Write rules to automatically tag your articles'
# admin: # admin:
# title: 'Administration' # title: 'Administration'
@ -404,6 +422,8 @@ tag:
new: new:
# add: 'Add' # add: 'Add'
# placeholder: 'You can add several tags, separated by a comma.' # placeholder: 'You can add several tags, separated by a comma.'
rename:
# placeholder: 'You can update tag name.'
# export: # 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>' # 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>'
@ -529,7 +549,8 @@ user:
email_label: 'E-mail' email_label: 'E-mail'
# enabled_label: 'Enabled' # enabled_label: 'Enabled'
# last_login_label: 'Last login' # last_login_label: 'Last login'
# twofactor_label: Two factor authentication # twofactor_email_label: Two factor authentication by email
# twofactor_google_label: Two factor authentication by OTP app
# save: Save # save: Save
# delete: Delete # delete: Delete
# delete_confirm: Are you sure? # delete_confirm: Are you sure?
@ -567,10 +588,10 @@ flashes:
password_updated: 'Parolă actualizată' password_updated: 'Parolă actualizată'
password_not_updated_demo: "In demonstration mode, you can't change password for this user." password_not_updated_demo: "In demonstration mode, you can't change password for this user."
user_updated: 'Informație actualizată' user_updated: 'Informație actualizată'
rss_updated: 'Informație RSS actualizată' feed_updated: 'Informație RSS actualizată'
# tagging_rules_updated: 'Tagging rules updated' # tagging_rules_updated: 'Tagging rules updated'
# tagging_rules_deleted: 'Tagging rule deleted' # tagging_rules_deleted: 'Tagging rule deleted'
# rss_token_updated: 'RSS token updated' # feed_token_updated: 'RSS token updated'
# annotations_reset: Annotations reset # annotations_reset: Annotations reset
# tags_reset: Tags reset # tags_reset: Tags reset
# entries_reset: Entries reset # entries_reset: Entries reset
@ -588,9 +609,11 @@ flashes:
entry_starred: 'Articol adăugat la favorite' entry_starred: 'Articol adăugat la favorite'
entry_unstarred: 'Articol șters de la favorite' entry_unstarred: 'Articol șters de la favorite'
entry_deleted: 'Articol șters' entry_deleted: 'Articol șters'
# no_random_entry: 'No article with these criterias was found'
tag: tag:
notice: notice:
# tag_added: 'Tag added' # tag_added: 'Tag added'
# tag_renamed: 'Tag renamed'
import: import:
notice: notice:
# failed: 'Import failed, please try again.' # failed: 'Import failed, please try again.'

View file

@ -36,6 +36,7 @@ menu:
add_new_entry: 'Добавить новую запись' add_new_entry: 'Добавить новую запись'
search: 'Поиск' search: 'Поиск'
filter_entries: 'Фильтр записей' filter_entries: 'Фильтр записей'
# random_entry: Jump to a random entry from that list
export: 'Экспорт' export: 'Экспорт'
search_form: search_form:
input_label: 'Введите текст для поиска' input_label: 'Введите текст для поиска'
@ -52,11 +53,12 @@ config:
page_title: 'Настройки' page_title: 'Настройки'
tab_menu: tab_menu:
settings: 'Настройки' settings: 'Настройки'
rss: 'RSS' feed: 'RSS'
user_info: 'Информация о пользователе' user_info: 'Информация о пользователе'
password: 'Пароль' password: 'Пароль'
rules: 'Правила настройки простановки тегов' rules: 'Правила настройки простановки тегов'
new_user: 'Добавить пользователя' new_user: 'Добавить пользователя'
reset: 'Сброс данных'
form: form:
save: 'Сохранить' save: 'Сохранить'
form_settings: form_settings:
@ -81,24 +83,31 @@ config:
help_reading_speed: "wallabag посчитает сколько времени занимает чтение каждой записи. Вы можете определить здесь, как быстро вы читаете. wallabag пересчитает время чтения для каждой записи." help_reading_speed: "wallabag посчитает сколько времени занимает чтение каждой записи. Вы можете определить здесь, как быстро вы читаете. wallabag пересчитает время чтения для каждой записи."
help_language: "Вы можете изменить язык интерфейса wallabag." help_language: "Вы можете изменить язык интерфейса wallabag."
help_pocket_consumer_key: "Обязательно для импорта из Pocket. Вы можете создать это в Вашем аккаунте на Pocket." help_pocket_consumer_key: "Обязательно для импорта из Pocket. Вы можете создать это в Вашем аккаунте на Pocket."
form_rss: form_feed:
description: 'RSS фид созданный с помощью wallabag позволяет читать Ваши записи через Ваш любимый RSS агрегатор. Для начала Вам потребуется создать ключ.' description: 'RSS фид созданный с помощью wallabag позволяет читать Ваши записи через Ваш любимый RSS агрегатор. Для начала Вам потребуется создать ключ.'
token_label: 'RSS ключ' token_label: 'RSS ключ'
no_token: 'Ключ не задан' no_token: 'Ключ не задан'
token_create: 'Создать ключ' token_create: 'Создать ключ'
token_reset: 'Пересоздать ключ' token_reset: 'Пересоздать ключ'
rss_links: 'ссылка на RSS' feed_links: 'ссылка на RSS'
rss_link: feed_link:
unread: 'непрочитанные' unread: 'непрочитанные'
starred: 'помеченные' starred: 'помеченные'
archive: 'архивные' archive: 'архивные'
rss_limit: 'Количество записей в фиде' feed_limit: 'Количество записей в фиде'
form_user: form_user:
two_factor_description: "Включить двухфакторную аутентификацию, Вы получите сообщение на указанный email с кодом, при каждом новом непроверенном подключении." # two_factor_description: "Enabling two factor authentication means you'll receive an email with a code OR need to use an OTP app (like Google Authenticator, Authy or FreeOTP) to get a one time code on every new untrusted connection. You can't choose both option."
name_label: 'Имя' name_label: 'Имя'
email_label: 'Email' email_label: 'Email'
twoFactorAuthentication_label: 'Двухфакторная аутентификация' # emailTwoFactor_label: 'Using email (receive a code by email)'
help_twoFactorAuthentication: "Если Вы включите двухфакторную аутентификацию, то Вы будете получать код на указанный ранее email, каждый раз при входе в wallabag." # googleTwoFactor_label: 'Using an OTP app (open the app, like Google Authenticator, Authy or FreeOTP, to get a one time code)'
# table_method: Method
# table_state: State
# table_action: Action
# state_enabled: Enabled
# state_disabled: Disabled
# action_email: Use email
# action_app: Use OTP App
delete: delete:
title: "Удалить мой аккаунт (или опасная зона)" title: "Удалить мой аккаунт (или опасная зона)"
description: "Если Вы удалите ваш аккаунт, ВСЕ ваши записи, теги и другие данные, будут БЕЗВОЗВРАТНО удалены (операция не может быть отменена после). Затем Вы выйдете из системы." description: "Если Вы удалите ваш аккаунт, ВСЕ ваши записи, теги и другие данные, будут БЕЗВОЗВРАТНО удалены (операция не может быть отменена после). Затем Вы выйдете из системы."
@ -154,6 +163,15 @@ config:
or: 'Одно правило ИЛИ другое' or: 'Одно правило ИЛИ другое'
and: 'Одно правило И другое' and: 'Одно правило И другое'
matches: 'Тесты, в которых <i> тема </i> соответствует <i> поиску </i> (без учета регистра). Пример: <code> title matches "футбол" </code>' matches: 'Тесты, в которых <i> тема </i> соответствует <i> поиску </i> (без учета регистра). Пример: <code> title matches "футбол" </code>'
otp:
# page_title: Two-factor authentication
# app:
# two_factor_code_description_1: You just enabled the OTP two factor authentication, open your OTP app and use that code to get a one time password. It'll disapear after a page reload.
# two_factor_code_description_2: 'You can scan that QR Code with your app:'
# two_factor_code_description_3: 'Also, save these backup codes in a safe place, you can use them in case you lose access to your OTP app:'
# two_factor_code_description_4: 'Test an OTP code from your configured app:'
# cancel: Cancel
# enable: Enable
entry: entry:
default_title: 'Название записи' default_title: 'Название записи'
@ -341,7 +359,7 @@ quickstart:
title: 'Настроить приложение' title: 'Настроить приложение'
description: 'Чтобы иметь приложение, которое вам подходит, ознакомьтесь с конфигурацией wallabag.' description: 'Чтобы иметь приложение, которое вам подходит, ознакомьтесь с конфигурацией wallabag.'
language: 'Выбрать язык и дизайн' language: 'Выбрать язык и дизайн'
rss: 'Включить RSS фид' feed: 'Включить RSS фид'
tagging_rules: 'Создать правило для автоматической установки тегов' tagging_rules: 'Создать правило для автоматической установки тегов'
admin: admin:
title: 'Администрирование' title: 'Администрирование'
@ -392,6 +410,8 @@ tag:
new: new:
add: 'Добавить' add: 'Добавить'
placeholder: 'Вы можете добавить несколько тегов, разделенных запятой.' placeholder: 'Вы можете добавить несколько тегов, разделенных запятой.'
rename:
# placeholder: 'You can update tag name.'
# export: # 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>' # 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>'
@ -517,7 +537,8 @@ user:
email_label: 'Email' email_label: 'Email'
enabled_label: 'Включить' enabled_label: 'Включить'
last_login_label: 'Последний вход' last_login_label: 'Последний вход'
twofactor_label: "Двухфакторная аутентификация" # twofactor_email_label: Two factor authentication by email
# twofactor_google_label: Two factor authentication by OTP app
save: "Сохранить" save: "Сохранить"
delete: "Удалить" delete: "Удалить"
delete_confirm: "Вы уверены?" delete_confirm: "Вы уверены?"
@ -533,10 +554,10 @@ flashes:
password_updated: 'Пароль обновлен' password_updated: 'Пароль обновлен'
password_not_updated_demo: "В режиме демонстрации нельзя изменять пароль для этого пользователя." password_not_updated_demo: "В режиме демонстрации нельзя изменять пароль для этого пользователя."
user_updated: 'Информация обновлена' user_updated: 'Информация обновлена'
rss_updated: 'RSS информация обновлена' feed_updated: 'RSS информация обновлена'
tagging_rules_updated: 'Правила тегировния обновлены' tagging_rules_updated: 'Правила тегировния обновлены'
tagging_rules_deleted: 'Правила тегировния удалены' tagging_rules_deleted: 'Правила тегировния удалены'
rss_token_updated: 'RSS ключ обновлен' feed_token_updated: 'RSS ключ обновлен'
annotations_reset: "Аннотации сброшены" annotations_reset: "Аннотации сброшены"
tags_reset: "Теги сброшены" tags_reset: "Теги сброшены"
entries_reset: "Записи сброшены" entries_reset: "Записи сброшены"
@ -553,9 +574,11 @@ flashes:
entry_starred: 'Запись помечена звездочкой' entry_starred: 'Запись помечена звездочкой'
entry_unstarred: 'Пометка звездочкой у записи убрана' entry_unstarred: 'Пометка звездочкой у записи убрана'
entry_deleted: 'Запись удалена' entry_deleted: 'Запись удалена'
# no_random_entry: 'No article with these criterias was found'
tag: tag:
notice: notice:
tag_added: 'Тег добавлен' tag_added: 'Тег добавлен'
# tag_renamed: 'Tag renamed'
import: import:
notice: notice:
failed: 'Во время импорта произошла ошибка, повторите попытку.' failed: 'Во время импорта произошла ошибка, повторите попытку.'
@ -573,4 +596,4 @@ flashes:
notice: notice:
added: 'Пользователь "%username%" добавлен' added: 'Пользователь "%username%" добавлен'
updated: 'Пользователь "%username%" обновлен' updated: 'Пользователь "%username%" обновлен'
deleted: 'Пользователь "%username%" удален' deleted: 'Пользователь "%username%" удален'

View file

@ -37,6 +37,7 @@ menu:
add_new_entry: 'เพิ่มรายการใหม่' add_new_entry: 'เพิ่มรายการใหม่'
search: 'ค้นหา' search: 'ค้นหา'
filter_entries: 'ตัวกรองรายการ' filter_entries: 'ตัวกรองรายการ'
# random_entry: Jump to a random entry from that list
export: 'นำข้อมูลออก' export: 'นำข้อมูลออก'
search_form: search_form:
input_label: 'ค้นหาที่นี้' input_label: 'ค้นหาที่นี้'
@ -53,11 +54,12 @@ config:
page_title: 'กำหนดค่า' page_title: 'กำหนดค่า'
tab_menu: tab_menu:
settings: 'ตั้งค่า' settings: 'ตั้งค่า'
rss: 'RSS' feed: 'RSS'
user_info: 'ข้อมูลผู้ใช้' user_info: 'ข้อมูลผู้ใช้'
password: 'รหัสผ่าน' password: 'รหัสผ่าน'
rules: 'การแท็กข้อบังคับ' rules: 'การแท็กข้อบังคับ'
new_user: 'เพิ่มผู้ใช้' new_user: 'เพิ่มผู้ใช้'
reset: 'รีเซ็ตพื้นที่ '
form: form:
save: 'บันทึก' save: 'บันทึก'
form_settings: form_settings:
@ -83,25 +85,32 @@ config:
help_reading_speed: "wallabag จะคำนวณเวลาการอ่านในแต่ละรายการซึ่งคุณสามารถกำหนดได้ที่นี้,ต้องขอบคุณรายการนี้,หากคุณเป็นนักอ่านที่เร็วหรือช้า wallabag จะทำการคำนวณเวลาที่อ่านใหม่ในแต่ละรายการ" help_reading_speed: "wallabag จะคำนวณเวลาการอ่านในแต่ละรายการซึ่งคุณสามารถกำหนดได้ที่นี้,ต้องขอบคุณรายการนี้,หากคุณเป็นนักอ่านที่เร็วหรือช้า wallabag จะทำการคำนวณเวลาที่อ่านใหม่ในแต่ละรายการ"
help_language: "คุณสามารถเปลี่ยภาษาของ wallabag interface ได้" help_language: "คุณสามารถเปลี่ยภาษาของ wallabag interface ได้"
help_pocket_consumer_key: "การ้องขอการเก็บการนำข้อมูลเข้า คุณสามารถสร้างบัญชีการเก็บของคุณ" help_pocket_consumer_key: "การ้องขอการเก็บการนำข้อมูลเข้า คุณสามารถสร้างบัญชีการเก็บของคุณ"
form_rss: form_feed:
description: 'RSS จะเก็บเงื่อนไขโดย wallabag ต้องยอมรับการอ่านรายการของคุณกับผู้อ่านที่ชอบ RSS คุณต้องทำเครื่องหมายก่อน' description: 'RSS จะเก็บเงื่อนไขโดย wallabag ต้องยอมรับการอ่านรายการของคุณกับผู้อ่านที่ชอบ RSS คุณต้องทำเครื่องหมายก่อน'
token_label: 'เครื่องหมาย RSS' token_label: 'เครื่องหมาย RSS'
no_token: 'ไม่มีเครื่องหมาย' no_token: 'ไม่มีเครื่องหมาย'
token_create: 'สร้างเครื่องหมาย' token_create: 'สร้างเครื่องหมาย'
token_reset: 'ทำเครื่องหมาย' token_reset: 'ทำเครื่องหมาย'
rss_links: 'ลิงค์ RSS' feed_links: 'ลิงค์ RSS'
rss_link: feed_link:
unread: 'ยังไมได้่อ่าน' unread: 'ยังไมได้่อ่าน'
starred: 'ทำการแสดง' starred: 'ทำการแสดง'
archive: 'เอกสาร' archive: 'เอกสาร'
all: 'ทั้งหมด' all: 'ทั้งหมด'
rss_limit: 'จำนวนไอเทมที่เก็บ' feed_limit: 'จำนวนไอเทมที่เก็บ'
form_user: form_user:
two_factor_description: "การเปิดใช้งาน two factor authentication คือคุณจะต้องได้รับอีเมลกับ code ที่ยังไม่ตรวจสอบในการเชื่อมต่อ" # two_factor_description: "Enabling two factor authentication means you'll receive an email with a code OR need to use an OTP app (like Google Authenticator, Authy or FreeOTP) to get a one time code on every new untrusted connection. You can't choose both option."
name_label: 'ชื่อ' name_label: 'ชื่อ'
email_label: 'อีเมล' email_label: 'อีเมล'
twoFactorAuthentication_label: 'Two factor authentication' # emailTwoFactor_label: 'Using email (receive a code by email)'
help_twoFactorAuthentication: "ถ้าคุณเปิด 2FA, ในแต่ละช่วงเวลาที่คุณต้องการลงชื่อเข้าใช wallabag, คุณจะต้องได้รับ code จากอีเมล" # googleTwoFactor_label: 'Using an OTP app (open the app, like Google Authenticator, Authy or FreeOTP, to get a one time code)'
# table_method: Method
# table_state: State
# table_action: Action
# state_enabled: Enabled
# state_disabled: Disabled
# action_email: Use email
# action_app: Use OTP App
delete: delete:
title: ลบบัญชีของฉัน (โซนที่เป็นภัย!) title: ลบบัญชีของฉัน (โซนที่เป็นภัย!)
description: ถ้าคุณลบบัญชีของคุณIf , รายการทั้งหมดของคุณ, แท็กทั้งหมดของคุณ, หมายเหตุทั้งหมดของคุณและบัญชีของคุณจะถูกลบอย่างถาวร (มันไม่สามารถยกเลิกได้) คุณจะต้องลงชื่อออก description: ถ้าคุณลบบัญชีของคุณIf , รายการทั้งหมดของคุณ, แท็กทั้งหมดของคุณ, หมายเหตุทั้งหมดของคุณและบัญชีของคุณจะถูกลบอย่างถาวร (มันไม่สามารถยกเลิกได้) คุณจะต้องลงชื่อออก
@ -159,6 +168,15 @@ config:
and: 'หนึ่งข้อบังคับและอื่นๆ' and: 'หนึ่งข้อบังคับและอื่นๆ'
matches: 'ทดสอบว่า <i>เรื่อง</i> นี้ตรงกับ <i>การต้นหา</i> (กรณีไม่ทราบ).<br />ตัวอย่าง: <code>หัวข้อที่ตรงกับ "football"</code>' matches: 'ทดสอบว่า <i>เรื่อง</i> นี้ตรงกับ <i>การต้นหา</i> (กรณีไม่ทราบ).<br />ตัวอย่าง: <code>หัวข้อที่ตรงกับ "football"</code>'
notmatches: 'ทดสอบว่า <i>เรื่อง</i> นี้ไม่ตรงกับ <i>การต้นหา</i> (กรณีไม่ทราบ).<br />ตัวอย่าง: <code>หัวข้อทีไม่ตรงกับ "football"</code>' notmatches: 'ทดสอบว่า <i>เรื่อง</i> นี้ไม่ตรงกับ <i>การต้นหา</i> (กรณีไม่ทราบ).<br />ตัวอย่าง: <code>หัวข้อทีไม่ตรงกับ "football"</code>'
otp:
# page_title: Two-factor authentication
# app:
# two_factor_code_description_1: You just enabled the OTP two factor authentication, open your OTP app and use that code to get a one time password. It'll disapear after a page reload.
# two_factor_code_description_2: 'You can scan that QR Code with your app:'
# two_factor_code_description_3: 'Also, save these backup codes in a safe place, you can use them in case you lose access to your OTP app:'
# two_factor_code_description_4: 'Test an OTP code from your configured app:'
# cancel: Cancel
# enable: Enable
entry: entry:
default_title: 'หัวข้อรายการ' default_title: 'หัวข้อรายการ'
@ -351,7 +369,7 @@ quickstart:
title: 'กำหนดค่าแอพพลิเคชั่น' title: 'กำหนดค่าแอพพลิเคชั่น'
description: 'ภายใน order จะมี application suit ของคุณ, จะมองหาองค์ประกอบของ wallabag' description: 'ภายใน order จะมี application suit ของคุณ, จะมองหาองค์ประกอบของ wallabag'
language: 'เปลี่ยนภาษาและออกแบบ' language: 'เปลี่ยนภาษาและออกแบบ'
rss: 'เปิดใช้ RSS' feed: 'เปิดใช้ RSS'
tagging_rules: 'เขียนข้อบังคับการแท็กอัตโนมัติของบทความของคุณ' tagging_rules: 'เขียนข้อบังคับการแท็กอัตโนมัติของบทความของคุณ'
admin: admin:
title: 'ผู้ดูแลระบบ' title: 'ผู้ดูแลระบบ'
@ -402,6 +420,8 @@ tag:
new: new:
add: 'เพิ่ม' add: 'เพิ่ม'
placeholder: 'คุณสามารถเพิ่มได้หลายแท็ก, จากการแบ่งโดย comma' placeholder: 'คุณสามารถเพิ่มได้หลายแท็ก, จากการแบ่งโดย comma'
rename:
# placeholder: 'You can update tag name.'
export: 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>' 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>'
@ -527,7 +547,8 @@ user:
email_label: 'อีเมล' email_label: 'อีเมล'
enabled_label: 'เปิดใช้งาน' enabled_label: 'เปิดใช้งาน'
last_login_label: 'ลงชื้อเข้าใช้ครั้งสุดท้าย' last_login_label: 'ลงชื้อเข้าใช้ครั้งสุดท้าย'
twofactor_label: Two factor authentication # twofactor_email_label: Two factor authentication by email
# twofactor_google_label: Two factor authentication by OTP app
save: บันทึก save: บันทึก
delete: ลบ delete: ลบ
delete_confirm: ตุณแน่ใจหรือไม่? delete_confirm: ตุณแน่ใจหรือไม่?
@ -565,10 +586,10 @@ flashes:
password_updated: 'อัปเดตรหัสผ่าน' password_updated: 'อัปเดตรหัสผ่าน'
password_not_updated_demo: "In demonstration mode, you can't change password for this user." password_not_updated_demo: "In demonstration mode, you can't change password for this user."
user_updated: 'อัปเดตข้อมูล' user_updated: 'อัปเดตข้อมูล'
rss_updated: 'อัปเดตข้อมูล RSS' feed_updated: 'อัปเดตข้อมูล RSS'
tagging_rules_updated: 'อัปเดตการแท็กข้อบังคับ' tagging_rules_updated: 'อัปเดตการแท็กข้อบังคับ'
tagging_rules_deleted: 'การลบข้อบังคับของแท็ก' tagging_rules_deleted: 'การลบข้อบังคับของแท็ก'
rss_token_updated: 'อัปเดตเครื่องหมาย RSS ' feed_token_updated: 'อัปเดตเครื่องหมาย RSS '
annotations_reset: รีเซ็ตหมายเหตุ annotations_reset: รีเซ็ตหมายเหตุ
tags_reset: รีเซ็ตแท็ก tags_reset: รีเซ็ตแท็ก
entries_reset: รีเซ็ตรายการ entries_reset: รีเซ็ตรายการ
@ -586,9 +607,11 @@ flashes:
entry_starred: 'รายการที่แสดง' entry_starred: 'รายการที่แสดง'
entry_unstarred: 'รายการที่ไม่ได้แสดง' entry_unstarred: 'รายการที่ไม่ได้แสดง'
entry_deleted: 'รายการที่ถูกลบ' entry_deleted: 'รายการที่ถูกลบ'
# no_random_entry: 'No article with these criterias was found'
tag: tag:
notice: notice:
tag_added: 'แท็กที่เพิ่ม' tag_added: 'แท็กที่เพิ่ม'
# tag_renamed: 'Tag renamed'
import: import:
notice: notice:
failed: 'นำข้อมูลเข้าล้มเหลว, ลองใหม่อีกครั้ง' failed: 'นำข้อมูลเข้าล้มเหลว, ลองใหม่อีกครั้ง'

Some files were not shown because too many files have changed in this diff Show more