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_size = 2
[Makefile]
[*akefile]
indent_style = tab

View file

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

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
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
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
[![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)
# 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
terms of the MIT License. See the COPYING file for more details.

View file

@ -1,6 +1,7 @@
<?php
use Symfony\Component\Config\Loader\LoaderInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Kernel;
class AppKernel extends Kernel
@ -32,6 +33,8 @@ class AppKernel extends Kernel
new WhiteOctober\PagerfantaBundle\WhiteOctoberPagerfantaBundle(),
new FOS\JsRoutingBundle\FOSJsRoutingBundle(),
new BD\GuzzleSiteAuthenticatorBundle\BDGuzzleSiteAuthenticatorBundle(),
new OldSound\RabbitMqBundle\OldSoundRabbitMqBundle(),
new Http\HttplugBundle\HttplugBundle(),
// wallabag bundles
new Wallabag\CoreBundle\WallabagCoreBundle(),
@ -39,25 +42,32 @@ class AppKernel extends Kernel
new Wallabag\UserBundle\WallabagUserBundle(),
new Wallabag\ImportBundle\WallabagImportBundle(),
new Wallabag\AnnotationBundle\WallabagAnnotationBundle(),
new OldSound\RabbitMqBundle\OldSoundRabbitMqBundle(),
];
if (in_array($this->getEnvironment(), ['dev', 'test'], true)) {
$bundles[] = new Symfony\Bundle\DebugBundle\DebugBundle();
$bundles[] = new Symfony\Bundle\WebProfilerBundle\WebProfilerBundle();
$bundles[] = new Sensio\Bundle\DistributionBundle\SensioDistributionBundle();
$bundles[] = new Sensio\Bundle\GeneratorBundle\SensioGeneratorBundle();
$bundles[] = new Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle();
$bundles[] = new Symfony\Bundle\WebServerBundle\WebServerBundle();
if ('test' === $this->getEnvironment()) {
$bundles[] = new DAMA\DoctrineTestBundle\DAMADoctrineTestBundle();
}
if ('dev' === $this->getEnvironment()) {
$bundles[] = new Sensio\Bundle\GeneratorBundle\SensioGeneratorBundle();
$bundles[] = new Symfony\Bundle\WebServerBundle\WebServerBundle();
}
}
return $bundles;
}
public function getRootDir()
{
return __DIR__;
}
public function getCacheDir()
{
return dirname(__DIR__) . '/var/cache/' . $this->getEnvironment();
@ -70,7 +80,8 @@ class AppKernel extends Kernel
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) {
if ($container->getParameter('use_webpack_dev_server')) {
$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);
});
}
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;
}
.icon-rss {
.icon-feed {
background-color: #000;
color: #fff;
padding: 0.2em 0.5em;
@ -101,8 +101,8 @@ blockquote {
margin-bottom: 0.5em;
}
.icon-rss:hover,
.icon-rss:focus {
.icon-feed:hover,
.icon-feed:focus {
background-color: #fff;
color: #000;
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;
}

View file

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

View file

@ -197,6 +197,17 @@ a.original:not(.waves-effect) {
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 {
display: flex;
}

View file

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

View file

@ -8,6 +8,7 @@ function initFilters() {
$('#clear_form_filters').on('click', () => {
$('#filters input').val('');
$('#filters :checked').removeAttr('checked');
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:
dbal:
driver: "%database_driver%"
driver_class: "%database_driver_class%"
host: "%database_host%"
port: "%database_port%"
dbname: "%database_name%"
@ -55,7 +54,6 @@ doctrine:
charset: "%database_charset%"
path: "%database_path%"
unix_socket: "%database_socket%"
server_version: 5.6
orm:
auto_generate_proxy_classes: "%kernel.debug%"
@ -79,10 +77,13 @@ doctrine_migrations:
# Swiftmailer Configuration
swiftmailer:
transport: "%mailer_transport%"
host: "%mailer_host%"
username: "%mailer_user%"
password: "%mailer_password%"
transport: "%mailer_transport%"
username: "%mailer_user%"
password: "%mailer_password%"
host: "%mailer_host%"
port: "%mailer_port%"
encryption: "%mailer_encryption%"
auth_mode: "%mailer_auth_mode%"
spool:
type: memory
@ -197,10 +198,17 @@ fos_oauth_server:
refresh_token_lifetime: 1209600
scheb_two_factor:
trusted_computer:
trusted_device:
enabled: true
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:
enabled: "%twofactor_auth%"
@ -357,3 +365,30 @@ jms_serializer:
# see: https://github.com/schmittjoh/JMSSerializerBundle/pull/494
datetime:
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:
- { resource: config_dev.yml }
- { resource: parameters_test.yml }
- { resource: services_test.yml }
framework:
test: ~
@ -23,7 +24,6 @@ swiftmailer:
doctrine:
dbal:
driver: "%test_database_driver%"
driver_class: "%test_database_driver_class%"
host: "%test_database_host%"
port: "%test_database_port%"
dbname: "%test_database_name%"

View file

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

View file

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

View file

@ -51,3 +51,47 @@ craue_config_settings_modify:
fos_js_routing:
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
stateless: true
anonymous: true
provider: fos_userbundle
login_firewall:
logout_on_user_change: true
pattern: ^/login$
anonymous: ~
secured_area:
logout_on_user_change: true
pattern: ^/
form_login:
provider: fos_userbundle
@ -53,17 +56,27 @@ security:
path: /logout
target: /
two_factor:
provider: fos_userbundle
auth_form_path: 2fa_login
check_path: 2fa_login_check
access_control:
- { path: ^/api/doc, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/api/version, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/api/user, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/api/(doc|version|info|user), 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: ^/resetting, role: 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: ^/feed, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: /(unread|starred|archive).xml$, roles: IS_AUTHENTICATED_ANONYMOUSLY } # For backwards compatibility
- { path: ^/share, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/settings, roles: ROLE_SUPER_ADMIN }
- { path: ^/annotations, roles: ROLE_USER }
- { path: ^/2fa, role: IS_AUTHENTICATED_2FA_IN_PROGRESS }
- { path: ^/users, roles: ROLE_SUPER_ADMIN }
- { path: ^/, roles: ROLE_USER }

View file

@ -2,12 +2,6 @@ parameters:
lexik_form_filter.get_filter.doctrine_orm.class: Wallabag\CoreBundle\Event\Subscriber\CustomDoctrineORMSubscriber
services:
# used for tests
filesystem_cache:
class: Doctrine\Common\Cache\FilesystemCache
arguments:
- "%kernel.cache_dir%/doctrine/metadata"
twig.extension.text:
class: Twig_Extensions_Extension_Text
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:
test_database_driver: pdo_mysql
test_database_driver_class: ~
test_database_host: localhost
test_database_port: 3306
test_database_name: wallabag_test

View file

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

View file

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

View file

@ -6,19 +6,17 @@ use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Debug\Debug;
// 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);
set_time_limit(0);
/**
* @var Composer\Autoload\ClassLoader $loader
*/
$loader = require __DIR__.'/../app/autoload.php';
require __DIR__.'/../vendor/autoload.php';
$input = new ArgvInput();
$env = $input->getParameterOption(['--env', '-e'], getenv('SYMFONY_ENV') ?: 'dev');
$debug = getenv('SYMFONY_DEBUG') !== '0' && !$input->hasParameterOption(['--no-debug', '']) && $env !== 'prod';
$env = $input->getParameterOption(['--env', '-e'], getenv('SYMFONY_ENV') ?: 'dev', true);
$debug = getenv('SYMFONY_DEBUG') !== '0' && !$input->hasParameterOption('--no-debug', true) && $env !== 'prod';
if ($debug) {
Debug::enable();

View file

@ -2,7 +2,10 @@
"name": "wallabag/wallabag",
"type": "project",
"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",
"license": "MIT",
"authors": [
@ -28,7 +31,7 @@
"issues": "https://github.com/wallabag/wallabag/issues"
},
"require": {
"php": ">=5.6.0",
"php": ">=7.1.3",
"ext-pcre": "*",
"ext-dom": "*",
"ext-curl": "*",
@ -44,59 +47,68 @@
"ext-tokenizer": "*",
"ext-pdo": "*",
"ext-tidy": "*",
"symfony/symfony": "~3.3.13",
"doctrine/orm": "^2.5.12",
"doctrine/doctrine-bundle": "^1.8.0",
"doctrine/doctrine-cache-bundle": "^1.3.2",
"twig/extensions": "^1.5.1",
"symfony/swiftmailer-bundle": "^2.6.7",
"symfony/monolog-bundle": "^3.1.2",
"sensio/distribution-bundle": "^5.0.21",
"sensio/framework-extra-bundle": "^3.0.28",
"incenteev/composer-parameter-handler": "^2.1.2",
"symfony/symfony": "3.4.*",
"doctrine/orm": "^2.6",
"doctrine/doctrine-bundle": "^1.9",
"doctrine/doctrine-cache-bundle": "^1.3",
"twig/extensions": "^1.5",
"symfony/swiftmailer-bundle": "^3.2",
"symfony/monolog-bundle": "^3.1",
"sensio/distribution-bundle": "^5.0",
"sensio/framework-extra-bundle": "^5.2",
"incenteev/composer-parameter-handler": "^2.1",
"nelmio/cors-bundle": "~1.5",
"friendsofsymfony/rest-bundle": "~2.1",
"jms/serializer-bundle": "~2.2",
"nelmio/api-doc-bundle": "^2.13.2",
"mgargano/simplehtmldom": "~1.5",
"wallabag/tcpdf": "^6.2.15",
"wallabag/tcpdf": "^6.2.26",
"simplepie/simplepie": "~1.5",
"willdurand/hateoas-bundle": "~1.3",
"liip/theme-bundle": "^1.4.6",
"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/oauth-server-bundle": "^1.5.2",
"friendsofsymfony/oauth-server-bundle": "^1.5",
"stof/doctrine-extensions-bundle": "^1.2",
"scheb/two-factor-bundle": "^2.14.0",
"grandt/phpepub": "^4.0.7",
"wallabag/php-mobi": "~1.0.0",
"scheb/two-factor-bundle": "^3.0",
"grandt/phpepub": "dev-master",
"wallabag/php-mobi": "~1.0",
"kphoen/rulerz-bundle": "~0.13",
"guzzlehttp/guzzle": "^5.3.1",
"doctrine/doctrine-migrations-bundle": "^1.3",
"paragonie/random_compat": "^2.0.11",
"craue/config-bundle": "~2.0",
"craue/config-bundle": "dev-utf8mb4",
"mnapoli/piwik-twig-extension": "^1.0",
"ocramius/proxy-manager": "^1.0.2",
"white-october/pagerfanta-bundle": "^1.1.0",
"ocramius/proxy-manager": "^2.1.1",
"white-october/pagerfanta-bundle": "^1.1",
"php-amqplib/rabbitmq-bundle": "^1.14",
"predis/predis": "^1.1.1",
"predis/predis": "v1.1.x-dev",
"javibravo/simpleue": "^2.0",
"symfony/dom-crawler": "^3.3.13",
"friendsofsymfony/jsrouting-bundle": "^1.6.3",
"symfony/dom-crawler": "^3.4",
"friendsofsymfony/jsrouting-bundle": "^2.2",
"bdunogier/guzzle-site-authenticator": "^1.0.0",
"defuse/php-encryption": "^2.1",
"html2text/html2text": "^4.1",
"sulu/symfony-intl-fix": "^1.0"
"pragmarx/recovery": "^0.1.0",
"php-http/httplug-bundle": "^1.14"
},
"require-dev": {
"doctrine/doctrine-fixtures-bundle": "~2.2",
"doctrine/data-fixtures": "~1.1",
"doctrine/doctrine-fixtures-bundle": "~3.0",
"sensio/generator-bundle": "^3.0",
"symfony/phpunit-bridge": "^4.2",
"friendsofphp/php-cs-fixer": "~2.0",
"m6web/redis-mock": "^2.0",
"dama/doctrine-test-bundle": "^4.0"
"friendsofphp/php-cs-fixer": "~2.13",
"m6web/redis-mock": "^4.1",
"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": {
"post-cmd": [
@ -125,22 +137,40 @@
}
},
"autoload": {
"psr-4": { "Wallabag\\": "src/Wallabag/" },
"classmap": [ "app/AppKernel.php", "app/AppCache.php" ],
"exclude-from-classmap": [
"vendor/symfony/intl/Locale.php",
"vendor/symfony/symfony/src/Symfony/Component/Intl/Locale.php"
"psr-4": {
"Wallabag\\": "src/Wallabag/"
},
"classmap": [
"app/AppKernel.php",
"app/AppCache.php"
]
},
"autoload-dev": {
"psr-4": { "Tests\\": "tests/" }
"psr-4": {
"Tests\\": "tests/"
},
"files": [
"vendor/symfony/symfony/src/Symfony/Component/VarDumper/Resources/functions/dump.php"
]
},
"config": {
"bin-dir": "bin",
"platform": {
"php": "5.6.0"
"php": "7.1.3"
}
},
"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
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"
backupGlobals="false"
colors="true"
bootstrap="app/autoload.php"
bootstrap="vendor/autoload.php"
>
<testsuites>
@ -15,7 +15,7 @@
<php>
<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" />
</php>

View file

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

View file

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

View file

@ -4,18 +4,17 @@ namespace Wallabag\ApiBundle\Controller;
use Hateoas\Configuration\Route;
use Hateoas\Representation\Factory\PagerfantaFactory;
use JMS\Serializer\SerializationContext;
use Nelmio\ApiDocBundle\Annotation\ApiDoc;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Wallabag\CoreBundle\Entity\Entry;
use Wallabag\CoreBundle\Entity\Tag;
use Wallabag\CoreBundle\Event\EntryDeletedEvent;
use Wallabag\CoreBundle\Event\EntrySavedEvent;
use Wallabag\CoreBundle\Helper\UrlHasher;
class EntryRestController extends WallabagRestController
{
@ -29,8 +28,10 @@ class EntryRestController extends WallabagRestController
* @ApiDoc(
* 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"="url", "dataType"="string", "required"=true, "format"="An url", "description"="Url to check if it exists"},
* {"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"="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"="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)
{
$this->validateAuthentication();
$repo = $this->getDoctrine()->getRepository('WallabagCoreBundle:Entry');
$returnId = (null === $request->query->get('return_id')) ? false : (bool) $request->query->get('return_id');
$urls = $request->query->get('urls', []);
// handle multiple urls first
if (!empty($urls)) {
$results = [];
foreach ($urls as $url) {
$res = $this->getDoctrine()
->getRepository('WallabagCoreBundle:Entry')
->findByUrlAndUserId($url, $this->getUser()->getId());
$results[$url] = $this->returnExistInformation($res, $returnId);
}
return $this->sendResponse($results);
$hashedUrls = $request->query->get('hashed_urls', []);
$hashedUrl = $request->query->get('hashed_url', '');
if (!empty($hashedUrl)) {
$hashedUrls[] = $hashedUrl;
}
// let's see if it is a simple url?
$urls = $request->query->get('urls', []);
$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());
}
$res = $this->getDoctrine()
->getRepository('WallabagCoreBundle:Entry')
->findByUrlAndUserId($url, $this->getUser()->getId());
$results = [];
foreach ($hashedUrls as $hashedUrlToSearch) {
$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={
* {"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"="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"="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"="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"="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);
$tags = \is_array($request->query->get('tags')) ? '' : (string) $request->query->get('tags', '');
$since = $request->query->get('since', 0);
$detail = strtolower($request->query->get('detail', 'full'));
try {
/** @var \Pagerfanta\Pagerfanta $pager */
@ -116,7 +130,8 @@ class EntryRestController extends WallabagRestController
$sort,
$order,
$since,
$tags
$tags,
$detail
);
} catch (\Exception $e) {
throw new BadRequestHttpException($e->getMessage());
@ -140,8 +155,9 @@ class EntryRestController extends WallabagRestController
'perPage' => $perPage,
'tags' => $tags,
'since' => $since,
'detail' => $detail,
],
UrlGeneratorInterface::ABSOLUTE_URL
true
)
);
@ -349,9 +365,7 @@ class EntryRestController extends WallabagRestController
'language' => !empty($data['language']) ? $data['language'] : $entry->getLanguage(),
'date' => !empty($data['publishedAt']) ? $data['publishedAt'] : $entry->getPublishedAt(),
// faking the open graph preview picture
'open_graph' => [
'og_image' => !empty($data['picture']) ? $data['picture'] : $entry->getPreviewPicture(),
],
'image' => !empty($data['picture']) ? $data['picture'] : $entry->getPreviewPicture(),
'authors' => \is_string($data['authors']) ? explode(',', $data['authors']) : $entry->getPublishedBy(),
]
);
@ -363,7 +377,7 @@ class EntryRestController extends WallabagRestController
}
if (null !== $data['isArchived']) {
$entry->setArchived((bool) $data['isArchived']);
$entry->updateArchived((bool) $data['isArchived']);
}
if (null !== $data['isStarred']) {
@ -479,7 +493,7 @@ class EntryRestController extends WallabagRestController
}
if (null !== $data['isArchived']) {
$entry->setArchived((bool) $data['isArchived']);
$entry->updateArchived((bool) $data['isArchived']);
}
if (null !== $data['isStarred']) {
@ -787,21 +801,21 @@ class EntryRestController extends WallabagRestController
}
/**
* Shortcut to send data serialized in json.
*
* @param mixed $data
*
* @return JsonResponse
* Replace the hashedUrl keys in $results with the unhashed URL from the
* request, as recorded in $urlHashMap.
*/
private function sendResponse($data)
private function replaceUrlHashes(array $results, array $urlHashMap)
{
// https://github.com/schmittjoh/JMSSerializerBundle/issues/293
$context = new SerializationContext();
$context->setSerializeNull(true);
$newResults = [];
foreach ($results as $hash => $res) {
if (isset($urlHashMap[$hash])) {
$newResults[$urlHashMap[$hash]] = $res;
} else {
$newResults[$hash] = $res;
}
}
$json = $this->get('jms_serializer')->serialize($data, 'json', $context);
return (new JsonResponse())->setJson($json);
return $newResults;
}
/**
@ -832,8 +846,8 @@ class EntryRestController extends WallabagRestController
/**
* Return information about the entry if it exist and depending on the id or not.
*
* @param Entry|null $entry
* @param bool $returnId
* @param Entry|bool|null $entry
* @param bool $returnId
*
* @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;
use FOS\RestBundle\Controller\FOSRestController;
use JMS\Serializer\SerializationContext;
use Nelmio\ApiDocBundle\Annotation\ApiDoc;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
@ -14,6 +15,8 @@ class WallabagRestController extends FOSRestController
*
* @ApiDoc()
*
* @deprecated Should use info endpoint instead
*
* @return JsonResponse
*/
public function getVersionAction()
@ -24,6 +27,24 @@ class WallabagRestController extends FOSRestController
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()
{
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());
}
}
/**
* 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\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
{
@ -26,6 +42,7 @@ class AccessToken extends BaseAccessToken
/**
* @ORM\ManyToOne(targetEntity="Wallabag\UserBundle\Entity\User")
* @ORM\JoinColumn(name="user_id", referencedColumnName="id", onDelete="CASCADE")
*/
protected $user;
}

View file

@ -8,6 +8,22 @@ use FOS\OAuthServerBundle\Entity\AuthCode as BaseAuthCode;
/**
* @ORM\Table("oauth2_auth_codes")
* @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
{
@ -26,6 +42,7 @@ class AuthCode extends BaseAuthCode
/**
* @ORM\ManyToOne(targetEntity="Wallabag\UserBundle\Entity\User")
* @ORM\JoinColumn(name="user_id", referencedColumnName="id", onDelete="CASCADE")
*/
protected $user;
}

View file

@ -8,6 +8,22 @@ use FOS\OAuthServerBundle\Entity\RefreshToken as BaseRefreshToken;
/**
* @ORM\Table("oauth2_refresh_tokens")
* @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
{
@ -26,6 +42,7 @@ class RefreshToken extends BaseRefreshToken
/**
* @ORM\ManyToOne(targetEntity="Wallabag\UserBundle\Entity\User")
* @ORM\JoinColumn(name="user_id", referencedColumnName="id", onDelete="CASCADE")
*/
protected $user;
}

View file

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

View file

@ -3,6 +3,11 @@ entry:
resource: "WallabagApiBundle:EntryRest"
name_prefix: api_
search:
type: rest
resource: "WallabagApiBundle:SearchRest"
name_prefix: api_
tag:
type: rest
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>';
$help = '';
$conn = $this->getContainer()->get('doctrine')->getManager()->getConnection();
try {
$conn = $this->getContainer()->get('doctrine')->getManager()->getConnection();
$conn->connect();
} catch (\Exception $e) {
if (false === strpos($e->getMessage(), 'Unknown database')
@ -253,7 +254,7 @@ class InstallCommand extends ContainerAwareCommand
$question->setHidden(true);
$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->addRole('ROLE_SUPER_ADMIN');

View file

@ -57,7 +57,8 @@ class ShowUserCommand extends ContainerAwareCommand
sprintf('Display name: %s', $user->getName()),
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('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;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use PragmaRX\Recovery\Recovery as BackupCodes;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
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\TaggingRule;
use Wallabag\CoreBundle\Form\Type\ChangePasswordType;
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\UserInformationType;
use Wallabag\CoreBundle\Tools\Utils;
@ -45,7 +47,7 @@ class ConfigController extends Controller
$activeTheme = $this->get('liip_theme.active_theme');
$activeTheme->setName($config->getTheme());
$this->get('session')->getFlashBag()->add(
$this->addFlash(
'notice',
'flashes.config.notice.config_saved'
);
@ -67,7 +69,7 @@ class ConfigController extends Controller
$userManager->updateUser($user, true);
}
$this->get('session')->getFlashBag()->add('notice', $message);
$this->addFlash('notice', $message);
return $this->redirect($this->generateUrl('config') . '#set4');
}
@ -82,7 +84,7 @@ class ConfigController extends Controller
if ($userForm->isSubmitted() && $userForm->isValid()) {
$userManager->updateUser($user, true);
$this->get('session')->getFlashBag()->add(
$this->addFlash(
'notice',
'flashes.config.notice.user_updated'
);
@ -90,17 +92,17 @@ class ConfigController extends Controller
return $this->redirect($this->generateUrl('config') . '#set3');
}
// handle rss information
$rssForm = $this->createForm(RssType::class, $config, ['action' => $this->generateUrl('config') . '#set2']);
$rssForm->handleRequest($request);
// handle feed information
$feedForm = $this->createForm(FeedType::class, $config, ['action' => $this->generateUrl('config') . '#set2']);
$feedForm->handleRequest($request);
if ($rssForm->isSubmitted() && $rssForm->isValid()) {
if ($feedForm->isSubmitted() && $feedForm->isValid()) {
$em->persist($config);
$em->flush();
$this->get('session')->getFlashBag()->add(
$this->addFlash(
'notice',
'flashes.config.notice.rss_updated'
'flashes.config.notice.feed_updated'
);
return $this->redirect($this->generateUrl('config') . '#set2');
@ -130,7 +132,7 @@ class ConfigController extends Controller
$em->persist($taggingRule);
$em->flush();
$this->get('session')->getFlashBag()->add(
$this->addFlash(
'notice',
'flashes.config.notice.tagging_rules_updated'
);
@ -141,22 +143,134 @@ class ConfigController extends Controller
return $this->render('WallabagCoreBundle:Config:index.html.twig', [
'form' => [
'config' => $configForm->createView(),
'rss' => $rssForm->createView(),
'feed' => $feedForm->createView(),
'pwd' => $pwdForm->createView(),
'user' => $userForm->createView(),
'new_tagging_rule' => $newTaggingRule->createView(),
],
'rss' => [
'feed' => [
'username' => $user->getUsername(),
'token' => $config->getRssToken(),
'token' => $config->getFeedToken(),
],
'twofactor_auth' => $this->getParameter('twofactor_auth'),
'wallabag_url' => $this->getParameter('domain_name'),
'enabled_users' => $this->get('wallabag_user.user_repository')
->getSumEnabledUsers(),
'enabled_users' => $this->get('wallabag_user.user_repository')->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
*
@ -167,19 +281,19 @@ class ConfigController extends Controller
public function generateTokenAction(Request $request)
{
$config = $this->getConfig();
$config->setRssToken(Utils::generateToken());
$config->setFeedToken(Utils::generateToken());
$em = $this->getDoctrine()->getManager();
$em->persist($config);
$em->flush();
if ($request->isXmlHttpRequest()) {
return new JsonResponse(['token' => $config->getRssToken()]);
return new JsonResponse(['token' => $config->getFeedToken()]);
}
$this->get('session')->getFlashBag()->add(
$this->addFlash(
'notice',
'flashes.config.notice.rss_token_updated'
'flashes.config.notice.feed_token_updated'
);
return $this->redirect($this->generateUrl('config') . '#set2');
@ -202,7 +316,7 @@ class ConfigController extends Controller
$em->remove($rule);
$em->flush();
$this->get('session')->getFlashBag()->add(
$this->addFlash(
'notice',
'flashes.config.notice.tagging_rules_deleted'
);
@ -268,7 +382,7 @@ class ConfigController extends Controller
break;
}
$this->get('session')->getFlashBag()->add(
$this->addFlash(
'notice',
'flashes.config.notice.' . $type . '_reset'
);
@ -329,6 +443,27 @@ class ConfigController extends Controller
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.
*

View file

@ -2,12 +2,13 @@
namespace Wallabag\CoreBundle\Controller;
use Doctrine\ORM\NoResultException;
use Pagerfanta\Adapter\DoctrineORMAdapter;
use Pagerfanta\Exception\OutOfRangeCurrentPageException;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Cache;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Wallabag\CoreBundle\Entity\Entry;
use Wallabag\CoreBundle\Event\EntryDeletedEvent;
@ -232,6 +233,46 @@ class EntryController extends Controller
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.
*
@ -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
* It returns the response to be send.
@ -532,11 +525,9 @@ class EntryController extends Controller
switch ($type) {
case 'search':
$qb = $repository->getBuilderForSearchByUser($this->getUser()->getId(), $searchTerm, $currentRoute);
break;
case 'untagged':
$qb = $repository->getBuilderForUntaggedByUser($this->getUser()->getId());
break;
case 'starred':
$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.
*

View file

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

View file

@ -7,86 +7,97 @@ use Pagerfanta\Adapter\DoctrineORMAdapter;
use Pagerfanta\Exception\OutOfRangeCurrentPageException;
use Pagerfanta\Pagerfanta;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Wallabag\CoreBundle\Entity\Tag;
use Wallabag\UserBundle\Entity\User;
class RssController extends Controller
class FeedController extends Controller
{
/**
* Shows unread entries for current user.
*
* @Route("/{username}/{token}/unread.xml", name="unread_rss", defaults={"_format"="xml"})
* @ParamConverter("user", class="WallabagUserBundle:User", converter="username_rsstoken_converter")
* @Route("/feed/{username}/{token}/unread/{page}", name="unread_feed", defaults={"page"=1, "_format"="xml"})
*
* @ParamConverter("user", class="WallabagUserBundle:User", converter="username_feed_token_converter")
*
* @param User $user
* @param $page
*
* @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.
*
* @Route("/{username}/{token}/archive.xml", name="archive_rss", defaults={"_format"="xml"})
* @ParamConverter("user", class="WallabagUserBundle:User", converter="username_rsstoken_converter")
* @Route("/feed/{username}/{token}/archive/{page}", name="archive_feed", defaults={"page"=1, "_format"="xml"})
*
* @ParamConverter("user", class="WallabagUserBundle:User", converter="username_feed_token_converter")
*
* @param User $user
* @param $page
*
* @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.
*
* @Route("/{username}/{token}/starred.xml", name="starred_rss", defaults={"_format"="xml"})
* @ParamConverter("user", class="WallabagUserBundle:User", converter="username_rsstoken_converter")
* @Route("/feed/{username}/{token}/starred/{page}", name="starred_feed", defaults={"page"=1, "_format"="xml"})
*
* @ParamConverter("user", class="WallabagUserBundle:User", converter="username_feed_token_converter")
*
* @param User $user
* @param $page
*
* @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.
*
* @Route("/{username}/{token}/all.xml", name="all_rss", defaults={"_format"="xml"})
* @ParamConverter("user", class="WallabagUserBundle:User", converter="username_rsstoken_converter")
* @Route("/feed/{username}/{token}/all/{page}", name="all_feed", defaults={"page"=1, "_format"="xml"})
*
* @ParamConverter("user", class="WallabagUserBundle:User", converter="username_feed_token_converter")
*
* @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.
*
* @Route("/{username}/{token}/tags/{slug}.xml", name="tag_rss", defaults={"_format"="xml"})
* @ParamConverter("user", class="WallabagUserBundle:User", converter="username_rsstoken_converter")
* @Route("/feed/{username}/{token}/tags/{slug}/{page}", name="tag_feed", defaults={"page"=1, "_format"="xml"})
*
* @ParamConverter("user", class="WallabagUserBundle:User", converter="username_feed_token_converter")
* @ParamConverter("tag", options={"mapping": {"slug": "slug"}})
*
* @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(
'tag_rss',
'tag_feed',
[
'username' => $user->getUsername(),
'token' => $user->getConfig()->getRssToken(),
'token' => $user->getConfig()->getFeedToken(),
'slug' => $tag->getSlug(),
],
UrlGeneratorInterface::ABSOLUTE_URL
@ -119,12 +130,15 @@ class RssController extends Controller
return $this->render(
'@WallabagCore/themes/common/Entry/entries.xml.twig',
[
'url_html' => $this->generateUrl('tag_entries', ['slug' => $tag->getSlug()], UrlGeneratorInterface::ABSOLUTE_URL),
'type' => 'tag (' . $tag->getLabel() . ')',
'type' => 'tag',
'url' => $url,
'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);
$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);
$url = $this->generateUrl(
$type . '_rss',
$type . '_feed',
[
'username' => $user->getUsername(),
'token' => $user->getConfig()->getRssToken(),
'token' => $user->getConfig()->getFeedToken(),
],
UrlGeneratorInterface::ABSOLUTE_URL
);
@ -178,19 +192,19 @@ class RssController extends Controller
$entries->setCurrentPage((int) $page);
} catch (OutOfRangeCurrentPageException $e) {
if ($page > 1) {
return $this->redirect($url . '?page=' . $entries->getNbPages(), 302);
return $this->redirect($url . '/' . $entries->getNbPages());
}
}
return $this->render(
'@WallabagCore/themes/common/Entry/entries.xml.twig',
[
'url_html' => $this->generateUrl($type, [], UrlGeneratorInterface::ABSOLUTE_URL),
'type' => $type,
'url' => $url,
'entries' => $entries,
],
new Response('', 200, ['Content-Type' => 'application/rss+xml'])
return $this->render('@WallabagCore/themes/common/Entry/entries.xml.twig', [
'type' => $type,
'url' => $url,
'entries' => $entries,
'user' => $user->getUsername(),
'domainName' => $this->getParameter('domain_name'),
'version' => $this->getParameter('wallabag_core.version'),
],
new Response('', 200, ['Content-Type' => 'application/atom+xml'])
);
}
}

View file

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

View file

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

View file

@ -5,12 +5,13 @@ namespace Wallabag\CoreBundle\Controller;
use Pagerfanta\Adapter\ArrayAdapter;
use Pagerfanta\Exception\OutOfRangeCurrentPageException;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
use Wallabag\CoreBundle\Entity\Entry;
use Wallabag\CoreBundle\Entity\Tag;
use Wallabag\CoreBundle\Form\Type\NewTagType;
use Wallabag\CoreBundle\Form\Type\RenameTagType;
class TagController extends Controller
{
@ -87,8 +88,14 @@ class TagController extends Controller
$tags = $this->get('wallabag_core.tag_repository')
->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', [
'tags' => $tags,
'renameForms' => $renameForms,
]);
}
@ -130,4 +137,48 @@ class TagController extends Controller
'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
namespace Wallabag\CoreBundle\DataFixtures\ORM;
namespace Wallabag\CoreBundle\DataFixtures;
use Doctrine\Common\DataFixtures\AbstractFixture;
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Common\DataFixtures\DependentFixtureInterface;
use Doctrine\Common\Persistence\ObjectManager;
use Wallabag\CoreBundle\Entity\Config;
use Wallabag\UserBundle\DataFixtures\UserFixtures;
class LoadConfigData extends AbstractFixture implements OrderedFixtureInterface
class ConfigFixtures extends Fixture implements DependentFixtureInterface
{
/**
* {@inheritdoc}
@ -60,8 +61,10 @@ class LoadConfigData extends AbstractFixture implements OrderedFixtureInterface
/**
* {@inheritdoc}
*/
public function getOrder()
public function getDependencies()
{
return 20;
return [
UserFixtures::class,
];
}
}

View file

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

View file

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

View file

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

View file

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

View file

@ -1,13 +1,13 @@
<?php
namespace Wallabag\CoreBundle\DataFixtures\ORM;
namespace Wallabag\CoreBundle\DataFixtures;
use Doctrine\Common\DataFixtures\AbstractFixture;
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Common\DataFixtures\DependentFixtureInterface;
use Doctrine\Common\Persistence\ObjectManager;
use Wallabag\CoreBundle\Entity\TaggingRule;
class LoadTaggingRuleData extends AbstractFixture implements OrderedFixtureInterface
class TaggingRuleFixtures extends Fixture implements DependentFixtureInterface
{
/**
* {@inheritdoc}
@ -49,8 +49,10 @@ class LoadTaggingRuleData extends AbstractFixture implements OrderedFixtureInter
/**
* {@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.theme', $config['theme']);
$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.version', $config['version']);
$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;
use Doctrine\DBAL\Migrations\AbstractMigration;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

View file

@ -60,21 +60,21 @@ class Config
/**
* @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
*
* @ORM\Column(name="rss_limit", type="integer", nullable=true)
* @ORM\Column(name="feed_limit", type="integer", nullable=true)
* @Assert\Range(
* min = 1,
* max = 100000,
* maxMessage = "validator.rss_limit_too_high"
* maxMessage = "validator.feed_limit_too_high"
* )
*/
private $rssLimit;
private $feedLimit;
/**
* @var float
@ -231,51 +231,51 @@ class Config
}
/**
* Set rssToken.
* Set feed Token.
*
* @param string $rssToken
* @param string $feedToken
*
* @return Config
*/
public function setRssToken($rssToken)
public function setFeedToken($feedToken)
{
$this->rssToken = $rssToken;
$this->feedToken = $feedToken;
return $this;
}
/**
* Get rssToken.
* Get feedToken.
*
* @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
*/
public function setRssLimit($rssLimit)
public function setFeedLimit($feedLimit)
{
$this->rssLimit = $rssLimit;
$this->feedLimit = $feedLimit;
return $this;
}
/**
* Get rssLimit.
* Get Feed Limit.
*
* @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 Wallabag\AnnotationBundle\Entity\Annotation;
use Wallabag\CoreBundle\Helper\EntityTimestampsTrait;
use Wallabag\CoreBundle\Helper\UrlHasher;
use Wallabag\UserBundle\Entity\User;
/**
@ -25,7 +26,8 @@ use Wallabag\UserBundle\Entity\User;
* options={"collate"="utf8mb4_unicode_ci", "charset"="utf8mb4"},
* indexes={
* @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()
@ -75,6 +77,13 @@ class Entry
*/
private $url;
/**
* @var string
*
* @ORM\Column(name="hashed_url", type="string", length=40, nullable=true)
*/
private $hashedUrl;
/**
* @var bool
*
@ -86,6 +95,15 @@ class Entry
*/
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
*
@ -307,6 +325,7 @@ class Entry
public function setUrl($url)
{
$this->url = $url;
$this->hashedUrl = UrlHasher::hashUrl($url);
return $this;
}
@ -335,6 +354,44 @@ class Entry
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.
*
@ -357,7 +414,7 @@ class Entry
public function toggleArchive()
{
$this->isArchived = $this->isArchived() ^ 1;
$this->updateArchived($this->isArchived() ^ 1);
return $this;
}
@ -864,4 +921,24 @@ class Entry
{
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;
/**
* @var \DateTime
*
* @ORM\Column(name="updated_at", type="datetime")
*/
private $updatedAt;
/**
* @ORM\ManyToOne(targetEntity="Wallabag\UserBundle\Entity\User", inversedBy="siteCredentials")
*/
@ -178,6 +185,16 @@ class SiteCredential
return $this->createdAt;
}
/**
* Get updatedAt.
*
* @return \DateTime
*/
public function getUpdatedAt()
{
return $this->updatedAt;
}
/**
* @return User
*/

View file

@ -3,7 +3,7 @@
namespace Wallabag\CoreBundle\Entity;
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;
/**

View file

@ -6,8 +6,10 @@ use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
/**
* Stores the locale of the user in the session after the
* login. This can be used by the LocaleListener afterwards.
* Stores the locale of the user in the session after the login.
* 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
*/
@ -30,7 +32,7 @@ class UserLocaleListener
{
$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());
}
}

View file

@ -22,11 +22,13 @@ class EditEntryType extends AbstractType
'disabled' => true,
'required' => false,
'label' => 'entry.edit.url_label',
'default_protocol' => null,
])
->add('origin_url', UrlType::class, [
'required' => false,
'property_path' => 'originUrl',
'label' => 'entry.edit.origin_url_label',
'default_protocol' => null,
])
->add('save', SubmitType::class, [
'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\OptionsResolver\OptionsResolver;
class RssType extends AbstractType
class FeedType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('rss_limit', null, [
'label' => 'config.form_rss.rss_limit',
'property_path' => 'rssLimit',
->add('feed_limit', null, [
'label' => 'config.form_feed.feed_limit',
'property_path' => 'feedLimit',
])
->add('save', SubmitType::class, [
'label' => 'config.form.save',
@ -31,6 +31,6 @@ class RssType extends AbstractType
public function getBlockPrefix()
{
return 'rss_config';
return 'feed_config';
}
}

View file

@ -15,6 +15,7 @@ class NewEntryType extends AbstractType
->add('url', UrlType::class, [
'required' => true,
'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, [
'label' => 'config.form_user.email_label',
])
->add('twoFactorAuthentication', CheckboxType::class, [
->add('emailTwoFactor', CheckboxType::class, [
'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, [
'label' => 'config.form.save',

View file

@ -12,8 +12,8 @@ use Wallabag\CoreBundle\Entity\Entry;
use Wallabag\CoreBundle\Tools\Utils;
/**
* This kind of proxy class take care of getting the content from an url
* and update the entry with what it found.
* This kind of proxy class takes care of getting the content from an url
* and updates the entry with what it found.
*/
class ContentProxy
{
@ -54,7 +54,11 @@ class ContentProxy
if ((empty($content) || false === $this->validateContent($content)) && false === $disableContentUpdate) {
$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
// 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.
*
* @param $title
* @param $contentType
* @param string $title
* @param string $contentType
*
* @return string
*/
@ -253,16 +257,14 @@ class ContentProxy
if (!empty($content['title'])) {
$entry->setTitle($content['title']);
} elseif (!empty($content['open_graph']['og_title'])) {
$entry->setTitle($content['open_graph']['og_title']);
}
if (empty($content['html'])) {
$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'] .= $content['open_graph']['og_description'];
$content['html'] .= $content['description'];
}
}
@ -277,8 +279,8 @@ class ContentProxy
$entry->setPublishedBy($content['authors']);
}
if (!empty($content['all_headers']) && $this->storeArticleHeaders) {
$entry->setHeaders($content['all_headers']);
if (!empty($content['headers'])) {
$entry->setHeaders($content['headers']);
}
if (!empty($content['date'])) {
@ -289,17 +291,30 @@ class ContentProxy
$this->updateLanguage($entry, $content['language']);
}
if (!empty($content['open_graph']['og_image'])) {
$this->updatePreviewPicture($entry, $content['open_graph']['og_image']);
$previewPictureUrl = '';
if (!empty($content['image'])) {
$previewPictureUrl = $content['image'];
}
// 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)) {
$this->updatePreviewPicture($entry, $content['url']);
if (!empty($content['headers']['content-type']) && \in_array($this->mimeGuesser->guess($content['headers']['content-type']), ['jpeg', 'jpg', 'gif', 'png'], true)) {
$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'])) {
$entry->setMimetype($content['content_type']);
if (!empty($content['headers']['content-type'])) {
$entry->setMimetype($content['headers']['content-type']);
}
if (!empty($previewPictureUrl)) {
$this->updatePreviewPicture($entry, $previewPictureUrl);
}
try {

View file

@ -2,8 +2,13 @@
namespace Wallabag\CoreBundle\Helper;
use GuzzleHttp\Client;
use GuzzleHttp\Message\Response;
use Http\Client\Common\HttpMethodsClient;
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 Symfony\Component\DomCrawler\Crawler;
use Symfony\Component\Finder\Finder;
@ -19,9 +24,9 @@ class DownloadImages
private $mimeGuesser;
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->wallabagUrl = rtrim($wallabagUrl, '/');
$this->logger = $logger;
@ -30,6 +35,25 @@ class DownloadImages
$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.
*
@ -41,13 +65,7 @@ class DownloadImages
*/
public function processHtml($entryId, $html, $url)
{
$crawler = new Crawler($html);
$imagesCrawler = $crawler
->filterXpath('//img');
$imagesUrls = $imagesCrawler
->extract(['src']);
$imagesSrcsetUrls = $this->getSrcsetUrls($imagesCrawler);
$imagesUrls = array_unique(array_merge($imagesUrls, $imagesSrcsetUrls));
$imagesUrls = self::extractImagesUrlsFromHtml($html);
$relativePath = $this->getRelativePath($entryId);
@ -122,7 +140,7 @@ class DownloadImages
$localPath = $folderPath . '/' . $hashImage . '.' . $ext;
try {
$im = imagecreatefromstring($res->getBody());
$im = imagecreatefromstring((string) $res->getBody());
} catch (\Exception $e) {
$im = false;
}
@ -135,7 +153,21 @@ class DownloadImages
switch ($ext) {
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');
break;
case 'jpeg':
@ -185,7 +217,7 @@ class DownloadImages
*
* @return array An array of urls
*/
private function getSrcsetUrls(Crawler $imagesCrawler)
private static function getSrcsetUrls(Crawler $imagesCrawler)
{
$urls = [];
$iterator = $imagesCrawler
@ -279,14 +311,14 @@ class DownloadImages
/**
* Retrieve and validate the extension from the response of the url of the image.
*
* @param Response $res Guzzle Response
* @param string $imagePath Path from the src image from the content (used for log only)
* @param ResponseInterface $res Http Response
* @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
*/
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')]);
// ok header doesn't have the extension, try a different way

View file

@ -2,16 +2,18 @@
namespace Wallabag\CoreBundle\Helper;
use Graby\Ring\Client\SafeCurlHandler;
use GuzzleHttp\Client;
use GuzzleHttp\Client as GuzzleClient;
use GuzzleHttp\Cookie\CookieJar;
use GuzzleHttp\Event\SubscriberInterface;
use Http\Adapter\Guzzle5\Client as GuzzleAdapter;
use Http\Client\HttpClient;
use Http\HttplugBundle\ClientFactory\ClientFactory;
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] */
private $subscribers = [];
@ -36,29 +38,6 @@ class HttpClientFactory
$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.
*
@ -68,4 +47,34 @@ class HttpClientFactory
{
$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 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
*/

View file

@ -6,6 +6,7 @@ use Psr\Log\LoggerInterface;
use RulerZ\RulerZ;
use Wallabag\CoreBundle\Entity\Entry;
use Wallabag\CoreBundle\Entity\Tag;
use Wallabag\CoreBundle\Entity\TaggingRule;
use Wallabag\CoreBundle\Repository\EntryRepository;
use Wallabag\CoreBundle\Repository\TagRepository;
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;
/**
* 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.
*
* @see http://stfalcon.com/en/blog/post/symfony2-custom-paramconverter
*/
class UsernameRssTokenConverter implements ParamConverterInterface
class UsernameFeedTokenConverter implements ParamConverterInterface
{
private $registry;
@ -67,7 +67,7 @@ class UsernameRssTokenConverter implements ParamConverterInterface
public function apply(Request $request, ParamConverter $configuration)
{
$username = $request->attributes->get('username');
$rssToken = $request->attributes->get('token');
$feedToken = $request->attributes->get('token');
if (!$request->attributes->has('username') || !$request->attributes->has('token')) {
return false;
@ -78,8 +78,8 @@ class UsernameRssTokenConverter implements ParamConverterInterface
$userRepository = $em->getRepository($configuration->getClass());
// Try to find user by its username and config rss_token
$user = $userRepository->findOneByUsernameAndRsstoken($username, $rssToken);
// Try to find user by its username and config feed_token
$user = $userRepository->findOneByUsernameAndFeedtoken($username, $feedToken);
if (null === $user || !($user instanceof User)) {
throw new NotFoundHttpException(sprintf('%s not found.', $configuration->getClass()));

View file

@ -3,11 +3,13 @@
namespace Wallabag\CoreBundle\Repository;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\NoResultException;
use Doctrine\ORM\QueryBuilder;
use Pagerfanta\Adapter\DoctrineORMAdapter;
use Pagerfanta\Pagerfanta;
use Wallabag\CoreBundle\Entity\Entry;
use Wallabag\CoreBundle\Entity\Tag;
use Wallabag\CoreBundle\Helper\UrlHasher;
class EntryRepository extends EntityRepository
{
@ -50,7 +52,7 @@ class EntryRepository extends EntityRepository
public function getBuilderForArchiveByUser($userId)
{
return $this
->getSortedQueryBuilderByUser($userId)
->getSortedQueryBuilderByUser($userId, 'archivedAt', 'desc')
->andWhere('e.isArchived = true')
;
}
@ -110,8 +112,7 @@ class EntryRepository extends EntityRepository
*/
public function getBuilderForUntaggedByUser($userId)
{
return $this
->sortQueryBuilder($this->getRawBuilderForUntaggedByUser($userId));
return $this->sortQueryBuilder($this->getRawBuilderForUntaggedByUser($userId));
}
/**
@ -139,15 +140,30 @@ class EntryRepository extends EntityRepository
* @param string $order
* @param int $since
* @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
*/
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')
->leftJoin('e.tags', 't')
->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) {
$qb->andWhere('e.isArchived = :isArchived')->setParameter('isArchived', (bool) $isArchived);
}
@ -193,6 +209,8 @@ class EntryRepository extends EntityRepository
$qb->orderBy('e.id', $order);
} elseif ('updated' === $sort) {
$qb->orderBy('e.updatedAt', $order);
} elseif ('archived' === $sort) {
$qb->orderBy('e.archivedAt', $order);
}
$pagerAdapter = new DoctrineORMAdapter($qb, true, false);
@ -324,15 +342,32 @@ class EntryRepository extends EntityRepository
* Find an entry by its url and its owner.
* If it exists, return the entry otherwise return false.
*
* @param $url
* @param $userId
* @param string $url
* @param int $userId
*
* @return Entry|bool
*/
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')
->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)
->getQuery()
->getResult();
@ -416,8 +451,8 @@ class EntryRepository extends EntityRepository
/**
* Find all entries by url and owner.
*
* @param $url
* @param $userId
* @param string $url
* @param int $userId
*
* @return array
*/
@ -430,6 +465,49 @@ class EntryRepository extends EntityRepository
->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.
*
@ -468,7 +546,6 @@ class EntryRepository extends EntityRepository
*/
private function sortQueryBuilder(QueryBuilder $qb, $sortBy = 'createdAt', $direction = 'desc')
{
return $qb
->orderBy(sprintf('e.%s', $sortBy), $direction);
return $qb->orderBy(sprintf('e.%s', $sortBy), $direction);
}
}

View file

@ -22,10 +22,10 @@ services:
tags:
- { name: form.type }
wallabag_core.param_converter.username_rsstoken_converter:
class: Wallabag\CoreBundle\ParamConverter\UsernameRssTokenConverter
wallabag_core.param_converter.username_feed_token_converter:
class: Wallabag\CoreBundle\ParamConverter\UsernameFeedTokenConverter
tags:
- { name: request.param_converter, converter: username_rsstoken_converter }
- { name: request.param_converter, converter: username_feed_token_converter }
arguments:
- "@doctrine"
@ -42,7 +42,7 @@ services:
-
error_message: '%wallabag_core.fetching_error_message%'
error_message_title: '%wallabag_core.fetching_error_message_title%'
- "@wallabag_core.guzzle.http_client"
- "@wallabag_core.http_client"
- "@wallabag_core.graby.config_builder"
calls:
- [ setLogger, [ "@logger" ] ]
@ -55,9 +55,8 @@ services:
- {}
- "@logger"
wallabag_core.guzzle.http_client:
class: GuzzleHttp\ClientInterface
factory: ["@wallabag_core.guzzle.http_client_factory", buildHttpClient]
wallabag_core.http_client:
alias: 'httplug.client.wallabag_core'
wallabag_core.guzzle_authenticator.config_builder:
class: Wallabag\CoreBundle\GuzzleSiteAuthenticator\GrabySiteConfigBuilder
@ -73,7 +72,7 @@ services:
bd_guzzle_site_authenticator.site_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
arguments:
- "@wallabag_core.guzzle.cookie_jar"
@ -181,6 +180,7 @@ services:
wallabag_core.exception_controller:
class: Wallabag\CoreBundle\Controller\ExceptionController
public: true
arguments:
- '@twig'
- '%kernel.debug%'
@ -211,10 +211,38 @@ services:
- "@logger"
wallabag_core.entry.download_images.client:
class: GuzzleHttp\Client
alias: 'httplug.client.wallabag_core.entry.download_images'
wallabag_core.helper.crypto_proxy:
class: Wallabag\CoreBundle\Helper\CryptoProxy
arguments:
- "%wallabag_core.site_credentials.encryption_key_path%"
- "@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'
search: 'Søg'
filter_entries: 'Filtrer artikler'
# random_entry: Jump to a random entry from that list
# export: 'Export'
search_form:
input_label: 'Indtast søgning'
@ -53,11 +54,12 @@ config:
page_title: 'Opsætning'
tab_menu:
settings: 'Indstillinger'
rss: 'RSS'
feed: 'RSS'
user_info: 'Brugeroplysninger'
password: 'Adgangskode'
# rules: 'Tagging rules'
new_user: 'Tilføj bruger'
# reset: 'Reset area'
form:
save: 'Gem'
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_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."
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.'
token_label: 'RSS-Token'
no_token: 'Intet token'
token_create: 'Opret token'
token_reset: 'Nulstil token'
rss_links: 'RSS-Links'
rss_link:
feed_links: 'RSS-Links'
feed_link:
unread: 'Ulæst'
starred: 'Favoritter'
archive: 'Arkiv'
# all: 'All'
# rss_limit: 'Number of items in the feed'
# feed_limit: 'Number of items in the feed'
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'
email_label: 'Emailadresse'
# twoFactorAuthentication_label: 'Two factor authentication'
# help_twoFactorAuthentication: "If you enable 2FA, each time you want to login to wallabag, you'll receive a code by email."
two_factor:
# 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:
# 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.
@ -159,6 +169,15 @@ config:
# 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>'
# 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:
# default_title: 'Title of the entry'
@ -353,7 +372,7 @@ quickstart:
# title: 'Configure the application'
# description: 'In order to have an application which suits you, have a look into the configuration of wallabag.'
# language: 'Change language and design'
# rss: 'Enable RSS feeds'
# feed: 'Enable RSS feeds'
# tagging_rules: 'Write rules to automatically tag your articles'
# admin:
# title: 'Administration'
@ -404,6 +423,8 @@ tag:
new:
# add: 'Add'
# placeholder: 'You can add several tags, separated by a comma.'
rename:
# placeholder: 'You can update tag name.'
# export:
# footer_template: '<div style="text-align:center;"><p>Produced by wallabag with %method%</p><p>Please open <a href="https://github.com/wallabag/wallabag/issues">an issue</a> if you have trouble with the display of this E-Book on your device.</p></div>'
@ -529,7 +550,8 @@ user:
email_label: 'Emailadresse'
# enabled_label: 'Enabled'
# 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
# delete: Delete
# delete_confirm: Are you sure?
@ -567,10 +589,10 @@ flashes:
password_updated: 'Adgangskode opdateret'
# password_not_updated_demo: "In demonstration mode, you can't change password for this user."
user_updated: 'Oplysninger opdateret'
rss_updated: 'RSS-oplysninger opdateret'
feed_updated: 'RSS-oplysninger opdateret'
# tagging_rules_updated: 'Tagging rules updated'
# tagging_rules_deleted: 'Tagging rule deleted'
# rss_token_updated: 'RSS token updated'
# feed_token_updated: 'RSS token updated'
# annotations_reset: Annotations reset
# tags_reset: Tags reset
# entries_reset: Entries reset
@ -588,9 +610,11 @@ flashes:
entry_starred: 'Artikel markeret som favorit'
entry_unstarred: 'Artikel ikke længere markeret som favorit'
entry_deleted: 'Artikel slettet'
# no_random_entry: 'No article with these criterias was found'
tag:
notice:
# tag_added: 'Tag added'
# tag_renamed: 'Tag renamed'
import:
notice:
# failed: 'Import failed, please try again.'

View file

@ -37,6 +37,7 @@ menu:
add_new_entry: 'Neuen Artikel hinzufügen'
search: 'Suche'
filter_entries: 'Artikel filtern'
# random_entry: Jump to a random entry from that list
export: 'Exportieren'
search_form:
input_label: 'Suchbegriff hier eingeben'
@ -53,11 +54,12 @@ config:
page_title: 'Einstellungen'
tab_menu:
settings: 'Einstellungen'
rss: 'RSS'
feed: 'RSS'
user_info: 'Benutzerinformation'
password: 'Kennwort'
rules: 'Tagging-Regeln'
new_user: 'Benutzer hinzufügen'
reset: 'Zurücksetzen'
form:
save: 'Speichern'
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_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."
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.'
token_label: 'RSS-Token'
no_token: 'Kein Token'
token_create: 'Token erstellen'
token_reset: 'Token zurücksetzen'
rss_links: 'RSS-Links'
rss_link:
feed_links: 'RSS-Links'
feed_link:
unread: 'Ungelesene'
starred: 'Favoriten'
archive: 'Archivierte'
all: 'Alle'
rss_limit: 'Anzahl der Einträge pro Feed'
feed_limit: 'Anzahl der Einträge pro Feed'
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'
email_label: 'E-Mail-Adresse'
twoFactorAuthentication_label: 'Zwei-Faktor-Authentifizierung'
help_twoFactorAuthentication: "Wenn du 2FA aktivierst, wirst du bei jedem Login einen Code per E-Mail bekommen."
two_factor:
# 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:
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.'
@ -353,7 +363,7 @@ quickstart:
title: 'Anwendung konfigurieren'
description: 'Um die Applikation für dich anzupassen, schau in die Konfiguration von wallabag.'
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)'
admin:
title: 'Administration'
@ -404,6 +414,8 @@ tag:
new:
add: 'Hinzufügen'
placeholder: 'Du kannst verschiedene Tags, getrennt von einem Komma, hinzufügen.'
rename:
# placeholder: 'You can update tag name.'
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>'
@ -529,7 +541,8 @@ user:
email_label: 'E-Mail-Adresse'
enabled_label: 'Aktiviert'
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'
delete: 'Löschen'
delete_confirm: 'Bist du sicher?'
@ -567,14 +580,14 @@ flashes:
password_updated: 'Kennwort aktualisiert'
password_not_updated_demo: 'Im Testmodus kannst du das Kennwort nicht ändern.'
user_updated: 'Information aktualisiert'
rss_updated: 'RSS-Informationen aktualisiert'
feed_updated: 'RSS-Informationen aktualisiert'
tagging_rules_updated: 'Tagging-Regeln aktualisiert'
tagging_rules_deleted: 'Tagging-Regel gelöscht'
rss_token_updated: 'RSS-Token aktualisiert'
annotations_reset: 'Anmerkungen zurücksetzen'
tags_reset: 'Tags zurücksetzen'
entries_reset: 'Einträge zurücksetzen'
archived_reset: 'Archiverte Einträge zurücksetzen'
feed_token_updated: 'RSS-Token aktualisiert'
annotations_reset: Anmerkungen zurücksetzen
tags_reset: Tags zurücksetzen
entries_reset: Einträge zurücksetzen
archived_reset: Archiverte Einträge zurücksetzen
entry:
notice:
entry_already_saved: 'Eintrag bereits am %date% gespeichert'
@ -588,9 +601,11 @@ flashes:
entry_starred: 'Eintrag favorisiert'
entry_unstarred: 'Eintrag defavorisiert'
entry_deleted: 'Eintrag gelöscht'
# no_random_entry: 'No article with these criterias was found'
tag:
notice:
tag_added: 'Tag hinzugefügt'
#tag_renamed: 'Tag renamed'
import:
notice:
failed: 'Import fehlgeschlagen, bitte erneut probieren.'

View file

@ -37,6 +37,7 @@ menu:
add_new_entry: 'Add a new entry'
search: 'Search'
filter_entries: 'Filter entries'
random_entry: Jump to a random entry from that list
export: 'Export'
search_form:
input_label: 'Enter your search here'
@ -53,11 +54,12 @@ config:
page_title: 'Config'
tab_menu:
settings: 'Settings'
rss: 'RSS'
feed: 'Feeds'
user_info: 'User information'
password: 'Password'
rules: 'Tagging rules'
new_user: 'Add a user'
reset: 'Reset area'
form:
save: 'Save'
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_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."
form_rss:
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.'
token_label: 'RSS token'
form_feed:
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: 'Feed token'
no_token: 'No token'
token_create: 'Create your token'
token_reset: 'Regenerate your token'
rss_links: 'RSS links'
rss_link:
feed_links: 'Feed links'
feed_link:
unread: 'Unread'
starred: 'Starred'
archive: 'Archived'
all: 'All'
rss_limit: 'Number of items in the feed'
feed_limit: 'Number of items in the feed'
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'
email_label: 'Email'
twoFactorAuthentication_label: 'Two factor authentication'
help_twoFactorAuthentication: "If you enable 2FA, each time you want to login to wallabag, you'll receive a code by email."
two_factor:
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:
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.
@ -159,6 +169,15 @@ config:
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>'
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:
default_title: 'Title of the entry'
@ -353,7 +372,7 @@ quickstart:
title: 'Configure the application'
description: 'In order to have an application which suits you, have a look into the configuration of wallabag.'
language: 'Change language and design'
rss: 'Enable RSS feeds'
feed: 'Enable feeds'
tagging_rules: 'Write rules to automatically tag your articles'
admin:
title: 'Administration'
@ -404,6 +423,8 @@ tag:
new:
add: 'Add'
placeholder: 'You can add several tags, separated by a comma.'
rename:
placeholder: 'You can update tag name.'
export:
footer_template: '<div style="text-align:center;"><p>Produced by wallabag with %method%</p><p>Please open <a href="https://github.com/wallabag/wallabag/issues">an issue</a> if you have trouble with the display of this E-Book on your device.</p></div>'
@ -529,7 +550,8 @@ user:
email_label: 'Email'
enabled_label: 'Enabled'
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
delete: Delete
delete_confirm: Are you sure?
@ -567,14 +589,15 @@ flashes:
password_updated: 'Password updated'
password_not_updated_demo: "In demonstration mode, you can't change password for this user."
user_updated: 'Information updated'
rss_updated: 'RSS information updated'
feed_updated: 'Feed information updated'
tagging_rules_updated: 'Tagging rules updated'
tagging_rules_deleted: 'Tagging rule deleted'
rss_token_updated: 'RSS token updated'
feed_token_updated: 'Feed token updated'
annotations_reset: Annotations reset
tags_reset: Tags reset
entries_reset: Entries reset
archived_reset: Archived entries deleted
otp_enabled: Two-factor authentication enabled
entry:
notice:
entry_already_saved: 'Entry already saved on %date%'
@ -588,9 +611,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:
notice:
tag_added: 'Tag added'
tag_renamed: 'Tag renamed'
import:
notice:
failed: 'Import failed, please try again.'

View file

@ -37,6 +37,7 @@ menu:
add_new_entry: 'Añadir un nuevo artículo'
search: 'Buscar'
filter_entries: 'Filtrar los artículos'
# random_entry: Jump to a random entry from that list
export: 'Exportar'
search_form:
input_label: 'Introduzca su búsqueda aquí'
@ -53,11 +54,12 @@ config:
page_title: 'Configuración'
tab_menu:
settings: 'Configuración'
rss: 'RSS'
feed: 'RSS'
user_info: 'Información de usuario'
password: 'Contraseña'
rules: 'Reglas de etiquetado automáticas'
new_user: 'Añadir un usuario'
reset: 'Reiniciar mi cuenta'
form:
save: 'Guardar'
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_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."
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.'
token_label: 'Token RSS'
no_token: 'Sin token'
token_create: 'Crear token'
token_reset: 'Reiniciar token'
rss_links: 'URLs de feeds RSS'
rss_link:
feed_links: 'URLs de feeds RSS'
feed_link:
unread: 'sin leer'
starred: 'favoritos'
archive: 'archivados'
# all: 'All'
rss_limit: 'Límite de artículos en feed RSS'
feed_limit: 'Límite de artículos en feed RSS'
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'
email_label: 'Dirección de e-mail'
twoFactorAuthentication_label: 'Autenticación en dos pasos'
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."
two_factor:
# 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:
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.
@ -159,6 +169,15 @@ config:
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>'
# 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:
default_title: 'Título del artículo'
@ -353,7 +372,7 @@ quickstart:
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.'
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'
admin:
title: 'Administración'
@ -404,6 +423,8 @@ tag:
new:
add: 'Añadir'
placeholder: 'Puedes añadir varias etiquetas, separadas por una coma.'
rename:
# placeholder: 'You can update tag name.'
# export:
# footer_template: '<div style="text-align:center;"><p>Produced by wallabag with %method%</p><p>Please open <a href="https://github.com/wallabag/wallabag/issues">an issue</a> if you have trouble with the display of this E-Book on your device.</p></div>'
@ -529,7 +550,8 @@ user:
email_label: 'E-mail'
enabled_label: 'Activado'
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
delete: Eliminar
delete_confirm: ¿Estás seguro?
@ -567,10 +589,10 @@ flashes:
password_updated: 'Contraseña actualizada'
password_not_updated_demo: "En el modo demo, no puede cambiar la contraseña del usuario."
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_deleted: 'Regla de etiquetado eliminada'
rss_token_updated: 'Token RSS actualizado'
feed_token_updated: 'Token RSS actualizado'
annotations_reset: Anotaciones reiniciadas
tags_reset: Etiquetas reiniciadas
entries_reset: Artículos reiniciados
@ -588,9 +610,11 @@ flashes:
entry_starred: 'Artículo marcado como favorito'
entry_unstarred: 'Artículo desmarcado como favorito'
entry_deleted: 'Artículo eliminado'
# no_random_entry: 'No article with these criterias was found'
tag:
notice:
tag_added: 'Etiqueta añadida'
# tag_renamed: 'Tag renamed'
import:
notice:
failed: 'Importación fallida, por favor, inténtelo de nuevo.'

View file

@ -37,6 +37,7 @@ menu:
add_new_entry: 'افزودن مقالهٔ تازه'
search: 'جستجو'
filter_entries: 'فیلترکردن مقاله‌ها'
# random_entry: Jump to a random entry from that list
export: 'برون‌بری'
search_form:
input_label: 'جستجوی خود را این‌جا بنویسید:'
@ -53,11 +54,12 @@ config:
page_title: 'پیکربندی'
tab_menu:
settings: 'تنظیمات'
rss: 'آر-اس-اس'
feed: 'آر-اس-اس'
user_info: 'اطلاعات کاربر'
password: 'رمز'
rules: 'برچسب‌گذاری خودکار'
new_user: 'افزودن کاربر'
# reset: 'Reset area'
form:
save: 'ذخیره'
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_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."
form_rss:
form_feed:
description: 'با خوراک آر-اس-اس که wallabag در اختیارتان می‌گذارد، می‌توانید مقاله‌های ذخیره‌شده را در نرم‌افزار آر-اس-اس دلخواه خود بخوانید. برای این کار نخست باید یک کد بسازید.'
token_label: 'کد آر-اس-اس'
no_token: 'بدون کد'
token_create: 'کد خود را بسازید'
token_reset: 'بازنشانی کد'
rss_links: 'پیوند آر-اس-اس'
rss_link:
feed_links: 'پیوند آر-اس-اس'
feed_link:
unread: 'خوانده‌نشده'
starred: 'برگزیده'
archive: 'بایگانی'
# all: 'All'
rss_limit: 'محدودیت آر-اس-اس'
feed_limit: 'محدودیت آر-اس-اس'
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: 'نام'
email_label: 'نشانی ایمیل'
twoFactorAuthentication_label: 'تأیید ۲مرحله‌ای'
# help_twoFactorAuthentication: "If you enable 2FA, each time you want to login to wallabag, you'll receive a code by email."
two_factor:
# 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:
# 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.
@ -159,6 +169,15 @@ config:
# 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>'
# 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:
# default_title: 'Title of the entry'
@ -353,7 +372,7 @@ quickstart:
title: 'برنامه را تنظیم کنید'
# description: 'In order to have an application which suits you, have a look into the configuration of wallabag.'
language: 'زبان و نمای برنامه را تغییر دهید'
rss: 'خوراک آر-اس-اس را فعال کنید'
feed: 'خوراک آر-اس-اس را فعال کنید'
tagging_rules: 'قانون‌های برچسب‌گذاری خودکار مقاله‌هایتان را تعریف کنید'
admin:
title: 'مدیریت'
@ -404,6 +423,8 @@ tag:
new:
# add: 'Add'
# placeholder: 'You can add several tags, separated by a comma.'
rename:
# placeholder: 'You can update tag name.'
# export:
# footer_template: '<div style="text-align:center;"><p>Produced by wallabag with %method%</p><p>Please open <a href="https://github.com/wallabag/wallabag/issues">an issue</a> if you have trouble with the display of this E-Book on your device.</p></div>'
@ -529,7 +550,8 @@ user:
email_label: 'نشانی ایمیل'
# enabled_label: 'Enabled'
# 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
# delete: Delete
# delete_confirm: Are you sure?
@ -567,10 +589,10 @@ flashes:
password_updated: 'رمز به‌روز شد'
password_not_updated_demo: "در حالت نمایشی نمی‌توانید رمز کاربر را عوض کنید."
user_updated: 'اطلاعات به‌روز شد'
rss_updated: 'اطلاعات آر-اس-اس به‌روز شد'
feed_updated: 'اطلاعات آر-اس-اس به‌روز شد'
tagging_rules_updated: 'برچسب‌گذاری خودکار به‌روز شد'
tagging_rules_deleted: 'قانون برچسب‌گذاری پاک شد'
rss_token_updated: 'کد آر-اس-اس به‌روز شد'
feed_token_updated: 'کد آر-اس-اس به‌روز شد'
# annotations_reset: Annotations reset
# tags_reset: Tags reset
# entries_reset: Entries reset
@ -588,9 +610,11 @@ flashes:
entry_starred: 'مقاله برگزیده شد'
entry_unstarred: 'مقاله نابرگزیده شد'
entry_deleted: 'مقاله پاک شد'
# no_random_entry: 'No article with these criterias was found'
tag:
notice:
tag_added: 'برچسب افزوده شد'
# tag_renamed: 'Tag renamed'
import:
notice:
failed: 'درون‌ریزی شکست خورد. لطفاً دوباره تلاش کنید.'

View file

@ -37,6 +37,7 @@ menu:
add_new_entry: "Sauvegarder un nouvel article"
search: "Rechercher"
filter_entries: "Filtrer les articles"
random_entry: Aller à un article aléatoire de cette liste
export: "Exporter"
search_form:
input_label: "Saisissez votre terme de recherche"
@ -53,11 +54,12 @@ config:
page_title: "Configuration"
tab_menu:
settings: "Paramètres"
rss: "RSS"
feed: "Flux"
user_info: "Mon compte"
password: "Mot de passe"
rules: "Règles de tag automatiques"
new_user: "Créer un compte"
reset: "Réinitialisation"
form:
save: "Enregistrer"
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_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."
form_rss:
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."
token_label: "Jeton RSS"
form_feed:
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 de flux"
no_token: "Aucun jeton généré"
token_create: "Créez votre jeton"
token_reset: "Réinitialisez votre jeton"
rss_links: "Adresses de vos flux RSS"
rss_link:
feed_links: "Adresses de vos flux"
feed_link:
unread: "Non lus"
starred: "Favoris"
archive: "Lus"
all: "Tous"
rss_limit: "Nombre darticles dans le flux"
feed_limit: "Nombre darticles dans le flux"
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"
email_label: "Adresse courriel"
twoFactorAuthentication_label: "Double authentification"
help_twoFactorAuthentication: "Si vous activez 2FA, à chaque tentative de connexion à wallabag, vous recevrez un code par email."
two_factor:
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:
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é."
@ -159,6 +169,15 @@ config:
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>"
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:
default_title: "Titre de larticle"
@ -353,7 +372,7 @@ quickstart:
title: "Configurez lapplication"
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"
rss: "Activez les flux RSS"
feed: "Activez les flux Atom"
tagging_rules: "Écrivez des règles pour classer automatiquement vos articles"
admin:
title: "Administration"
@ -404,6 +423,8 @@ tag:
new:
add: "Ajouter"
placeholder: "Vous pouvez ajouter plusieurs tags, séparés par une virgule."
rename:
placeholder: 'Vous pouvez changer le nom de votre tag.'
export:
footer_template: '<div style="text-align:center;"><p>Généré par wallabag with %method%</p><p>Merci d''ouvrir <a href="https://github.com/wallabag/wallabag/issues">un ticket</a> si vous rencontrez des soucis d''affichage avec ce document sur votre support.</p></div>'
@ -530,6 +551,8 @@ user:
enabled_label: "Activé"
last_login_label: "Dernière connexion"
twofactor_label: "Double authentification"
twofactor_email_label: Double authentification par email
twofactor_google_label: Double authentification par OTP app
save: "Sauvegarder"
delete: "Supprimer"
delete_confirm: "Êtes-vous sûr ?"
@ -567,14 +590,15 @@ flashes:
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."
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_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"
tags_reset: "Tags supprimés"
entries_reset: "Articles supprimés"
archived_reset: "Articles archivés supprimés"
otp_enabled: "Authentification à double-facteur activée"
entry:
notice:
entry_already_saved: "Article déjà sauvegardé le %date%"
@ -588,9 +612,11 @@ flashes:
entry_starred: "Article ajouté dans les favoris"
entry_unstarred: "Article retiré des favoris"
entry_deleted: "Article supprimé"
no_random_entry: "Aucun article correspond aux critères n'a été trouvé"
tag:
notice:
tag_added: "Tag ajouté"
tag_renamed: "Tag renommé"
import:
notice:
failed: "Limport a échoué, veuillez ré-essayer"

View file

@ -37,6 +37,7 @@ menu:
add_new_entry: 'Aggiungi un nuovo contenuto'
search: 'Cerca'
filter_entries: 'Filtra contenuti'
# random_entry: Jump to a random entry from that list
export: 'Esporta'
search_form:
input_label: 'Inserisci qui la tua ricerca'
@ -53,11 +54,12 @@ config:
page_title: 'Configurazione'
tab_menu:
settings: 'Impostazioni'
rss: 'RSS'
feed: 'RSS'
user_info: 'Informazioni utente'
password: 'Password'
rules: 'Regole di etichettatura'
new_user: 'Aggiungi utente'
reset: 'Area di reset'
form:
save: 'Salva'
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_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."
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.'
token_label: 'Token RSS'
no_token: 'Nessun token'
token_create: 'Crea il tuo token'
token_reset: 'Rigenera il tuo token'
rss_links: 'Collegamenti RSS'
rss_link:
feed_links: 'Collegamenti RSS'
feed_link:
unread: 'Non letti'
starred: 'Preferiti'
archive: 'Archiviati'
# all: 'All'
rss_limit: 'Numero di elementi nel feed'
feed_limit: 'Numero di elementi nel feed'
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'
email_label: 'E-mail'
twoFactorAuthentication_label: 'Autenticazione a due fattori'
help_twoFactorAuthentication: "Se abiliti l'autenticazione a due fattori, ogni volta che vorrai connetterti a wallabag, riceverai un codice via 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:
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.
@ -159,6 +168,15 @@ config:
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>'
# 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:
default_title: "Titolo del contenuto"
@ -353,7 +371,7 @@ quickstart:
title: "Configura l'applicazione"
description: "Per avere un'applicazione che ti soddisfi, dai un'occhiata alla configurazione di wallabag."
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'
admin:
title: 'Amministrazione'
@ -404,6 +422,8 @@ tag:
new:
add: 'Aggiungi'
placeholder: 'Puoi aggiungere varie etichette, separate da una virgola.'
rename:
# placeholder: 'You can update tag name.'
# export:
# footer_template: '<div style="text-align:center;"><p>Produced by wallabag with %method%</p><p>Please open <a href="https://github.com/wallabag/wallabag/issues">an issue</a> if you have trouble with the display of this E-Book on your device.</p></div>'
@ -529,7 +549,8 @@ user:
email_label: 'E-mail'
enabled_label: 'Abilitato'
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
delete: Cancella
delete_confirm: Sei sicuro?
@ -567,10 +588,10 @@ flashes:
password_updated: 'Password aggiornata'
password_not_updated_demo: "In modalità demo, non puoi cambiare la password dell'utente."
user_updated: 'Informazioni aggiornate'
rss_updated: 'Informazioni RSS aggiornate'
feed_updated: 'Informazioni RSS aggiornate'
tagging_rules_updated: 'Regole di etichettatura aggiornate'
tagging_rules_deleted: 'Regola di etichettatura eliminate'
rss_token_updated: 'RSS token aggiornato'
feed_token_updated: 'RSS token aggiornato'
annotations_reset: Reset annotazioni
tags_reset: Reset etichette
entries_reset: Reset articoli
@ -588,9 +609,11 @@ flashes:
entry_starred: 'Contenuto segnato come preferito'
entry_unstarred: 'Contenuto rimosso dai preferiti'
entry_deleted: 'Contenuto eliminato'
# no_random_entry: 'No article with these criterias was found'
tag:
notice:
tag_added: 'Etichetta aggiunta'
# tag_renamed: 'Tag renamed'
import:
notice:
failed: 'Importazione fallita, riprova.'

View file

@ -37,6 +37,7 @@ menu:
add_new_entry: 'Enregistrar un novèl article'
search: 'Cercar'
filter_entries: 'Filtrar los articles'
# random_entry: Jump to a random entry from that list
export: 'Exportar'
search_form:
input_label: 'Picatz vòstre mot-clau a cercar aquí'
@ -53,11 +54,12 @@ config:
page_title: 'Configuracion'
tab_menu:
settings: 'Paramètres'
rss: 'RSS'
feed: 'RSS'
user_info: 'Mon compte'
password: 'Senhal'
rules: "Règlas d'etiquetas automaticas"
new_user: 'Crear un compte'
reset: 'Zòna de reïnicializacion'
form:
save: 'Enregistrar'
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_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."
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."
token_label: 'Geton RSS'
no_token: 'Pas cap de geton generat'
token_create: 'Creatz vòstre geton'
token_reset: 'Reïnicializatz vòstre geton'
rss_links: 'URLs de vòstres fluxes RSS'
rss_link:
feed_links: 'URLs de vòstres fluxes RSS'
feed_link:
unread: 'Pas legits'
starred: 'Favorits'
archive: 'Legits'
all: 'Totes'
rss_limit: "Nombre d'articles dins un flux RSS"
feed_limit: "Nombre d'articles dins un flux"
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'
email_label: 'Adreça de corrièl'
twoFactorAuthentication_label: 'Dobla autentificacion'
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."
# 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:
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.
@ -159,6 +168,15 @@ config:
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>'
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:
default_title: "Títol de l'article"
@ -353,7 +371,7 @@ quickstart:
title: "Configuratz l'aplicacion"
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"
rss: 'Activatz los fluxes RSS'
feed: 'Activatz los fluxes RSS'
tagging_rules: 'Escrivètz de règlas per classar automaticament vòstres articles'
admin:
title: 'Administracion'
@ -404,6 +422,8 @@ tag:
new:
add: 'Ajustar'
placeholder: "Podètz ajustar mai qu'una etiqueta, separadas per de virgula."
rename:
# placeholder: 'You can update tag name.'
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>'
@ -529,7 +549,8 @@ user:
email_label: 'Adreça de corrièl'
enabled_label: 'Actiu'
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'
delete: 'Suprimir'
delete_confirm: 'Sètz segur?'
@ -567,10 +588,10 @@ flashes:
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."
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_deleted: 'Règla suprimida'
rss_token_updated: 'Geton RSS mes a jorn'
feed_token_updated: 'Geton RSS mes a jorn'
annotations_reset: Anotacions levadas
tags_reset: Etiquetas levadas
entries_reset: Articles levats
@ -588,9 +609,11 @@ flashes:
entry_starred: 'Article ajustat dins los favorits'
entry_unstarred: 'Article quitat dels favorits'
entry_deleted: 'Article suprimit'
# no_random_entry: 'No article with these criterias was found'
tag:
notice:
tag_added: 'Etiqueta ajustada'
# tag_renamed: 'Tag renamed'
import:
notice:
failed: "L'importacion a fracassat, mercés de tornar ensajar."

View file

@ -37,6 +37,7 @@ menu:
add_new_entry: 'Dodaj nowy wpis'
search: 'Szukaj'
filter_entries: 'Filtruj wpisy'
# random_entry: Jump to a random entry from that list
export: 'Eksportuj'
search_form:
input_label: 'Wpisz swoje zapytanie tutaj'
@ -53,11 +54,12 @@ config:
page_title: 'Konfiguracja'
tab_menu:
settings: 'Ustawienia'
rss: 'Kanał RSS'
feed: 'Kanał RSS'
user_info: 'Informacje o użytkowniku'
password: 'Hasło'
rules: 'Zasady tagowania'
new_user: 'Dodaj użytkownika'
reset: 'Reset'
form:
save: 'Zapisz'
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_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."
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.'
token_label: 'Token RSS'
no_token: 'Brak tokena'
token_create: 'Stwórz tokena'
token_reset: 'Zresetuj swojego tokena'
rss_links: 'RSS links'
rss_link:
feed_links: 'RSS links'
feed_link:
unread: 'Nieprzeczytane'
starred: 'Oznaczone gwiazdką'
archive: 'Archiwum'
all: 'Wszystkie'
rss_limit: 'Link do RSS'
feed_limit: 'Link do RSS'
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'
email_label: 'Adres email'
twoFactorAuthentication_label: 'Autoryzacja dwuetapowa'
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."
# 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:
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.
@ -159,6 +168,15 @@ config:
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>'
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:
default_title: 'Tytuł wpisu'
@ -353,7 +371,7 @@ quickstart:
title: 'Konfiguruj aplikację'
description: 'W celu dopasowania aplikacji do swoich upodobań, zobacz konfigurację aplikacji'
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'
admin:
title: 'Administracja'
@ -404,6 +422,8 @@ tag:
new:
add: 'Dodaj'
placeholder: 'Możesz dodać kilka tagów, oddzielając je przecinkami.'
rename:
placeholder: 'Możesz zaktualizować nazwę taga.'
export:
footer_template: '<div style="text-align:center;"><p>Stworzone przez wallabag z %method%</p><p>Proszę zgłoś <a href="https://github.com/wallabag/wallabag/issues">sprawę</a>, jeżeli masz problem z wyświetleniem tego e-booka na swoim urządzeniu.</p></div>'
@ -529,7 +549,8 @@ user:
email_label: 'Adres email'
enabled_label: 'Włączony'
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
delete: Usuń
delete_confirm: Jesteś pewien?
@ -567,10 +588,10 @@ flashes:
password_updated: 'Hasło zaktualizowane'
password_not_updated_demo: "In demonstration mode, you can't change password for this user."
user_updated: 'Informacje zaktualizowane'
rss_updated: 'Informacje RSS zaktualizowane'
feed_updated: 'Informacje RSS zaktualizowane'
tagging_rules_updated: 'Reguły tagowania zaktualizowane'
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
tags_reset: Zresetuj tagi
entries_reset: Zresetuj wpisy
@ -588,9 +609,11 @@ flashes:
entry_starred: 'Wpis oznaczony gwiazdką'
entry_unstarred: 'Wpis odznaczony gwiazdką'
entry_deleted: 'Wpis usunięty'
# no_random_entry: 'No article with these criterias was found'
tag:
notice:
tag_added: 'Tag dodany'
tag_renamed: 'Nazwa taga zmieniona'
import:
notice:
failed: 'Nieudany import, prosimy spróbować ponownie.'

View file

@ -37,6 +37,7 @@ menu:
add_new_entry: 'Adicionar uma nova entrada'
search: 'Pesquisa'
filter_entries: 'Filtrar entradas'
# random_entry: Jump to a random entry from that list
export: 'Exportar'
search_form:
input_label: 'Digite aqui sua pesquisa'
@ -53,11 +54,12 @@ config:
page_title: 'Config'
tab_menu:
settings: 'Configurações'
rss: 'RSS'
feed: 'RSS'
user_info: 'Informação do Usuário'
password: 'Senha'
rules: 'Regras de tags'
new_user: 'Adicionar um usuário'
# reset: 'Reset area'
form:
save: 'Salvar'
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_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."
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.'
token_label: 'Token RSS'
no_token: 'Nenhum Token'
token_create: 'Criar seu token'
token_reset: 'Gerar novamente seu token'
rss_links: 'Links RSS'
rss_link:
feed_links: 'Links RSS'
feed_link:
unread: 'Não lido'
starred: 'Destacado'
archive: 'Arquivado'
# all: 'All'
rss_limit: 'Número de itens no feed'
feed_limit: 'Número de itens no feed'
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'
email_label: 'E-mail'
twoFactorAuthentication_label: 'Autenticação de dois passos'
# 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:
# 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.
@ -159,6 +168,15 @@ config:
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>'
# 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:
default_title: 'Título da entrada'
@ -353,7 +371,7 @@ quickstart:
title: 'Configurar a aplicação'
description: 'Para ter uma aplicação que atende você, dê uma olhada na configuração do wallabag.'
language: 'Alterar idioma e design'
rss: 'Habilitar feeds RSS'
feed: 'Habilitar feeds RSS'
tagging_rules: 'Escrever regras para acrescentar tags automaticamente em seus artigos'
admin:
title: 'Administração'
@ -404,6 +422,8 @@ tag:
new:
# add: 'Add'
# placeholder: 'You can add several tags, separated by a comma.'
rename:
# placeholder: 'You can update tag name.'
# export:
# footer_template: '<div style="text-align:center;"><p>Produced by wallabag with %method%</p><p>Please open <a href="https://github.com/wallabag/wallabag/issues">an issue</a> if you have trouble with the display of this E-Book on your device.</p></div>'
@ -529,7 +549,8 @@ user:
email_label: 'E-mail'
enabled_label: 'Habilitado'
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'
delete: 'Apagar'
delete_confirm: 'Tem certeza?'
@ -567,10 +588,10 @@ flashes:
password_updated: 'Senha atualizada'
password_not_updated_demo: 'Em modo de demonstração, você não pode alterar a senha deste usuário.'
# 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_deleted: 'Regra de tag apagada'
rss_token_updated: 'Token RSS atualizado'
feed_token_updated: 'Token RSS atualizado'
# annotations_reset: Annotations reset
# tags_reset: Tags reset
# entries_reset: Entries reset
@ -588,9 +609,11 @@ flashes:
entry_starred: 'Entrada destacada'
entry_unstarred: 'Entrada não destacada'
entry_deleted: 'Entrada apagada'
# no_random_entry: 'No article with these criterias was found'
tag:
notice:
tag_added: 'Tag adicionada'
# tag_renamed: 'Tag renamed'
import:
notice:
failed: 'Importação falhou, por favor tente novamente.'

View file

@ -37,6 +37,7 @@ menu:
add_new_entry: 'Introdu un nou articol'
search: 'Căutare'
filter_entries: 'Filtrează articolele'
# random_entry: Jump to a random entry from that list
# export: 'Export'
search_form:
input_label: 'Introdu căutarea ta'
@ -53,11 +54,12 @@ config:
page_title: 'Configurație'
tab_menu:
settings: 'Setări'
rss: 'RSS'
feed: 'RSS'
user_info: 'Informații despre utilizator'
password: 'Parolă'
# rules: 'Tagging rules'
new_user: 'Crează un utilizator'
# reset: 'Reset area'
form:
save: 'Salvează'
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_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."
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.'
token_label: 'RSS-Token'
no_token: 'Fără token'
token_create: 'Crează-ți token'
token_reset: 'Resetează-ți token-ul'
rss_links: 'Link-uri RSS'
rss_link:
feed_links: 'Link-uri RSS'
feed_link:
unread: 'Unread'
starred: 'Starred'
archive: 'Archived'
# all: 'All'
rss_limit: 'Limită RSS'
feed_limit: 'Limită RSS'
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'
email_label: 'E-mail'
# twoFactorAuthentication_label: 'Two factor authentication'
# 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:
# 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.
@ -159,6 +168,15 @@ config:
# 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>'
# 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:
# default_title: 'Title of the entry'
@ -353,7 +371,7 @@ quickstart:
# title: 'Configure the application'
# description: 'In order to have an application which suits you, have a look into the configuration of wallabag.'
# language: 'Change language and design'
# rss: 'Enable RSS feeds'
# feed: 'Enable RSS feeds'
# tagging_rules: 'Write rules to automatically tag your articles'
# admin:
# title: 'Administration'
@ -404,6 +422,8 @@ tag:
new:
# add: 'Add'
# placeholder: 'You can add several tags, separated by a comma.'
rename:
# placeholder: 'You can update tag name.'
# export:
# footer_template: '<div style="text-align:center;"><p>Produced by wallabag with %method%</p><p>Please open <a href="https://github.com/wallabag/wallabag/issues">an issue</a> if you have trouble with the display of this E-Book on your device.</p></div>'
@ -529,7 +549,8 @@ user:
email_label: 'E-mail'
# enabled_label: 'Enabled'
# 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
# delete: Delete
# delete_confirm: Are you sure?
@ -567,10 +588,10 @@ flashes:
password_updated: 'Parolă actualizată'
password_not_updated_demo: "In demonstration mode, you can't change password for this user."
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_deleted: 'Tagging rule deleted'
# rss_token_updated: 'RSS token updated'
# feed_token_updated: 'RSS token updated'
# annotations_reset: Annotations reset
# tags_reset: Tags reset
# entries_reset: Entries reset
@ -588,9 +609,11 @@ flashes:
entry_starred: 'Articol adăugat la favorite'
entry_unstarred: 'Articol șters de la favorite'
entry_deleted: 'Articol șters'
# no_random_entry: 'No article with these criterias was found'
tag:
notice:
# tag_added: 'Tag added'
# tag_renamed: 'Tag renamed'
import:
notice:
# failed: 'Import failed, please try again.'

View file

@ -36,6 +36,7 @@ menu:
add_new_entry: 'Добавить новую запись'
search: 'Поиск'
filter_entries: 'Фильтр записей'
# random_entry: Jump to a random entry from that list
export: 'Экспорт'
search_form:
input_label: 'Введите текст для поиска'
@ -52,11 +53,12 @@ config:
page_title: 'Настройки'
tab_menu:
settings: 'Настройки'
rss: 'RSS'
feed: 'RSS'
user_info: 'Информация о пользователе'
password: 'Пароль'
rules: 'Правила настройки простановки тегов'
new_user: 'Добавить пользователя'
reset: 'Сброс данных'
form:
save: 'Сохранить'
form_settings:
@ -81,24 +83,31 @@ config:
help_reading_speed: "wallabag посчитает сколько времени занимает чтение каждой записи. Вы можете определить здесь, как быстро вы читаете. wallabag пересчитает время чтения для каждой записи."
help_language: "Вы можете изменить язык интерфейса wallabag."
help_pocket_consumer_key: "Обязательно для импорта из Pocket. Вы можете создать это в Вашем аккаунте на Pocket."
form_rss:
form_feed:
description: 'RSS фид созданный с помощью wallabag позволяет читать Ваши записи через Ваш любимый RSS агрегатор. Для начала Вам потребуется создать ключ.'
token_label: 'RSS ключ'
no_token: 'Ключ не задан'
token_create: 'Создать ключ'
token_reset: 'Пересоздать ключ'
rss_links: 'ссылка на RSS'
rss_link:
feed_links: 'ссылка на RSS'
feed_link:
unread: 'непрочитанные'
starred: 'помеченные'
archive: 'архивные'
rss_limit: 'Количество записей в фиде'
feed_limit: 'Количество записей в фиде'
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: 'Имя'
email_label: 'Email'
twoFactorAuthentication_label: 'Двухфакторная аутентификация'
help_twoFactorAuthentication: "Если Вы включите двухфакторную аутентификацию, то Вы будете получать код на указанный ранее email, каждый раз при входе в wallabag."
# 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:
title: "Удалить мой аккаунт (или опасная зона)"
description: "Если Вы удалите ваш аккаунт, ВСЕ ваши записи, теги и другие данные, будут БЕЗВОЗВРАТНО удалены (операция не может быть отменена после). Затем Вы выйдете из системы."
@ -154,6 +163,15 @@ config:
or: 'Одно правило ИЛИ другое'
and: 'Одно правило И другое'
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:
default_title: 'Название записи'
@ -341,7 +359,7 @@ quickstart:
title: 'Настроить приложение'
description: 'Чтобы иметь приложение, которое вам подходит, ознакомьтесь с конфигурацией wallabag.'
language: 'Выбрать язык и дизайн'
rss: 'Включить RSS фид'
feed: 'Включить RSS фид'
tagging_rules: 'Создать правило для автоматической установки тегов'
admin:
title: 'Администрирование'
@ -392,6 +410,8 @@ tag:
new:
add: 'Добавить'
placeholder: 'Вы можете добавить несколько тегов, разделенных запятой.'
rename:
# placeholder: 'You can update tag name.'
# export:
# footer_template: '<div style="text-align:center;"><p>Produced by wallabag with %method%</p><p>Please open <a href="https://github.com/wallabag/wallabag/issues">an issue</a> if you have trouble with the display of this E-Book on your device.</p></div>'
@ -517,7 +537,8 @@ user:
email_label: 'Email'
enabled_label: 'Включить'
last_login_label: 'Последний вход'
twofactor_label: "Двухфакторная аутентификация"
# twofactor_email_label: Two factor authentication by email
# twofactor_google_label: Two factor authentication by OTP app
save: "Сохранить"
delete: "Удалить"
delete_confirm: "Вы уверены?"
@ -533,10 +554,10 @@ flashes:
password_updated: 'Пароль обновлен'
password_not_updated_demo: "В режиме демонстрации нельзя изменять пароль для этого пользователя."
user_updated: 'Информация обновлена'
rss_updated: 'RSS информация обновлена'
feed_updated: 'RSS информация обновлена'
tagging_rules_updated: 'Правила тегировния обновлены'
tagging_rules_deleted: 'Правила тегировния удалены'
rss_token_updated: 'RSS ключ обновлен'
feed_token_updated: 'RSS ключ обновлен'
annotations_reset: "Аннотации сброшены"
tags_reset: "Теги сброшены"
entries_reset: "Записи сброшены"
@ -553,9 +574,11 @@ flashes:
entry_starred: 'Запись помечена звездочкой'
entry_unstarred: 'Пометка звездочкой у записи убрана'
entry_deleted: 'Запись удалена'
# no_random_entry: 'No article with these criterias was found'
tag:
notice:
tag_added: 'Тег добавлен'
# tag_renamed: 'Tag renamed'
import:
notice:
failed: 'Во время импорта произошла ошибка, повторите попытку.'
@ -573,4 +596,4 @@ flashes:
notice:
added: 'Пользователь "%username%" добавлен'
updated: 'Пользователь "%username%" обновлен'
deleted: 'Пользователь "%username%" удален'
deleted: 'Пользователь "%username%" удален'

View file

@ -37,6 +37,7 @@ menu:
add_new_entry: 'เพิ่มรายการใหม่'
search: 'ค้นหา'
filter_entries: 'ตัวกรองรายการ'
# random_entry: Jump to a random entry from that list
export: 'นำข้อมูลออก'
search_form:
input_label: 'ค้นหาที่นี้'
@ -53,11 +54,12 @@ config:
page_title: 'กำหนดค่า'
tab_menu:
settings: 'ตั้งค่า'
rss: 'RSS'
feed: 'RSS'
user_info: 'ข้อมูลผู้ใช้'
password: 'รหัสผ่าน'
rules: 'การแท็กข้อบังคับ'
new_user: 'เพิ่มผู้ใช้'
reset: 'รีเซ็ตพื้นที่ '
form:
save: 'บันทึก'
form_settings:
@ -83,25 +85,32 @@ config:
help_reading_speed: "wallabag จะคำนวณเวลาการอ่านในแต่ละรายการซึ่งคุณสามารถกำหนดได้ที่นี้,ต้องขอบคุณรายการนี้,หากคุณเป็นนักอ่านที่เร็วหรือช้า wallabag จะทำการคำนวณเวลาที่อ่านใหม่ในแต่ละรายการ"
help_language: "คุณสามารถเปลี่ยภาษาของ wallabag interface ได้"
help_pocket_consumer_key: "การ้องขอการเก็บการนำข้อมูลเข้า คุณสามารถสร้างบัญชีการเก็บของคุณ"
form_rss:
form_feed:
description: 'RSS จะเก็บเงื่อนไขโดย wallabag ต้องยอมรับการอ่านรายการของคุณกับผู้อ่านที่ชอบ RSS คุณต้องทำเครื่องหมายก่อน'
token_label: 'เครื่องหมาย RSS'
no_token: 'ไม่มีเครื่องหมาย'
token_create: 'สร้างเครื่องหมาย'
token_reset: 'ทำเครื่องหมาย'
rss_links: 'ลิงค์ RSS'
rss_link:
feed_links: 'ลิงค์ RSS'
feed_link:
unread: 'ยังไมได้่อ่าน'
starred: 'ทำการแสดง'
archive: 'เอกสาร'
all: 'ทั้งหมด'
rss_limit: 'จำนวนไอเทมที่เก็บ'
feed_limit: 'จำนวนไอเทมที่เก็บ'
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: 'ชื่อ'
email_label: 'อีเมล'
twoFactorAuthentication_label: 'Two factor authentication'
help_twoFactorAuthentication: "ถ้าคุณเปิด 2FA, ในแต่ละช่วงเวลาที่คุณต้องการลงชื่อเข้าใช wallabag, คุณจะต้องได้รับ code จากอีเมล"
# 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:
title: ลบบัญชีของฉัน (โซนที่เป็นภัย!)
description: ถ้าคุณลบบัญชีของคุณIf , รายการทั้งหมดของคุณ, แท็กทั้งหมดของคุณ, หมายเหตุทั้งหมดของคุณและบัญชีของคุณจะถูกลบอย่างถาวร (มันไม่สามารถยกเลิกได้) คุณจะต้องลงชื่อออก
@ -159,6 +168,15 @@ config:
and: 'หนึ่งข้อบังคับและอื่นๆ'
matches: 'ทดสอบว่า <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:
default_title: 'หัวข้อรายการ'
@ -351,7 +369,7 @@ quickstart:
title: 'กำหนดค่าแอพพลิเคชั่น'
description: 'ภายใน order จะมี application suit ของคุณ, จะมองหาองค์ประกอบของ wallabag'
language: 'เปลี่ยนภาษาและออกแบบ'
rss: 'เปิดใช้ RSS'
feed: 'เปิดใช้ RSS'
tagging_rules: 'เขียนข้อบังคับการแท็กอัตโนมัติของบทความของคุณ'
admin:
title: 'ผู้ดูแลระบบ'
@ -402,6 +420,8 @@ tag:
new:
add: 'เพิ่ม'
placeholder: 'คุณสามารถเพิ่มได้หลายแท็ก, จากการแบ่งโดย comma'
rename:
# placeholder: 'You can update tag name.'
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>'
@ -527,7 +547,8 @@ user:
email_label: 'อีเมล'
enabled_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: บันทึก
delete: ลบ
delete_confirm: ตุณแน่ใจหรือไม่?
@ -565,10 +586,10 @@ flashes:
password_updated: 'อัปเดตรหัสผ่าน'
password_not_updated_demo: "In demonstration mode, you can't change password for this user."
user_updated: 'อัปเดตข้อมูล'
rss_updated: 'อัปเดตข้อมูล RSS'
feed_updated: 'อัปเดตข้อมูล RSS'
tagging_rules_updated: 'อัปเดตการแท็กข้อบังคับ'
tagging_rules_deleted: 'การลบข้อบังคับของแท็ก'
rss_token_updated: 'อัปเดตเครื่องหมาย RSS '
feed_token_updated: 'อัปเดตเครื่องหมาย RSS '
annotations_reset: รีเซ็ตหมายเหตุ
tags_reset: รีเซ็ตแท็ก
entries_reset: รีเซ็ตรายการ
@ -586,9 +607,11 @@ flashes:
entry_starred: 'รายการที่แสดง'
entry_unstarred: 'รายการที่ไม่ได้แสดง'
entry_deleted: 'รายการที่ถูกลบ'
# no_random_entry: 'No article with these criterias was found'
tag:
notice:
tag_added: 'แท็กที่เพิ่ม'
# tag_renamed: 'Tag renamed'
import:
notice:
failed: 'นำข้อมูลเข้าล้มเหลว, ลองใหม่อีกครั้ง'

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