mirror of
https://github.com/wallabag/wallabag.git
synced 2025-12-24 12:47:52 +01:00
Compare commits
56 Commits
change-rea
...
feat/sort
| Author | SHA1 | Date | |
|---|---|---|---|
| 5981cfb8b8 | |||
| 7e8a3c432f | |||
| a97e84a21e | |||
| 0371188aa4 | |||
| 61b1bb9261 | |||
| 7e6de2117f | |||
| 7c563b967e | |||
| 4517cb3b0a | |||
| b1502333a6 | |||
| 69b6983df6 | |||
| 2ee88ffe4e | |||
| 96133123da | |||
| f6ca2b9ff8 | |||
| 6c77dd1d15 | |||
| f906f62a3f | |||
| 6fa6ebc0a4 | |||
| b443e9388d | |||
| 925c08fa83 | |||
| 896e81b229 | |||
| efaa129ef1 | |||
| 75f9e2a144 | |||
| 61a28a5ca2 | |||
| 0be919c8fd | |||
| c2ae69edbb | |||
| 8b6f1696f4 | |||
| 6f76578a51 | |||
| 3ad06d69a5 | |||
| b09224cac1 | |||
| 3f6c01103d | |||
| 396221376d | |||
| d5c316dcc4 | |||
| 24e240ca03 | |||
| 96f15e0246 | |||
| 10bd1ed3d3 | |||
| 487643c5f6 | |||
| ebc066ec62 | |||
| 5b2e36f4d7 | |||
| faeb9c5049 | |||
| 1064b65ed0 | |||
| b4e22ef367 | |||
| bdc13b519e | |||
| 365ca09d8e | |||
| 9539880690 | |||
| 30f77732e5 | |||
| db8acc217e | |||
| 9303a2bad8 | |||
| 9926868030 | |||
| 90e2212b9d | |||
| 62c01abdc7 | |||
| 27cd2cd190 | |||
| acc1496f60 | |||
| 3e3650a7a8 | |||
| be8ea09f74 | |||
| ce2ac8f758 | |||
| dbab3c1041 | |||
| 42c87ab1d7 |
2
.github/CODEOWNERS
vendored
Normal file
2
.github/CODEOWNERS
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
# Migrated rules from dependabot.yml
|
||||
composer.* @Kdecherf @j0k3r @yguedidi
|
||||
4
.github/dependabot.yml
vendored
4
.github/dependabot.yml
vendored
@ -35,10 +35,6 @@ updates:
|
||||
phpstan-dependencies:
|
||||
patterns:
|
||||
- "phpstan/*"
|
||||
reviewers:
|
||||
- j0k3r
|
||||
- yguedidi
|
||||
- Kdecherf
|
||||
ignore:
|
||||
- dependency-name: symfony/*
|
||||
update-types: [ "version-update:semver-major" ]
|
||||
|
||||
2
.github/workflows/coding-standards.yml
vendored
2
.github/workflows/coding-standards.yml
vendored
@ -17,7 +17,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: "Checkout"
|
||||
uses: "actions/checkout@v4"
|
||||
uses: "actions/checkout@v5"
|
||||
|
||||
- name: "Install PHP"
|
||||
uses: "shivammathur/setup-php@v2"
|
||||
|
||||
6
.github/workflows/continuous-integration.yml
vendored
6
.github/workflows/continuous-integration.yml
vendored
@ -41,7 +41,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: "Checkout"
|
||||
uses: "actions/checkout@v4"
|
||||
uses: "actions/checkout@v5"
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
@ -116,7 +116,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: "Checkout"
|
||||
uses: "actions/checkout@v4"
|
||||
uses: "actions/checkout@v5"
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
@ -187,7 +187,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: "Checkout"
|
||||
uses: "actions/checkout@v4"
|
||||
uses: "actions/checkout@v5"
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
|
||||
2
.github/workflows/translations.yml
vendored
2
.github/workflows/translations.yml
vendored
@ -22,7 +22,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: "Checkout"
|
||||
uses: "actions/checkout@v4"
|
||||
uses: "actions/checkout@v5"
|
||||
|
||||
- name: "Install PHP"
|
||||
uses: "shivammathur/setup-php@v2"
|
||||
|
||||
2
.github/workflows/upload-release-package.yml
vendored
2
.github/workflows/upload-release-package.yml
vendored
@ -16,7 +16,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: "Checkout"
|
||||
uses: "actions/checkout@v4"
|
||||
uses: "actions/checkout@v5"
|
||||
|
||||
- name: "Install PHP"
|
||||
uses: "shivammathur/setup-php@v2"
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# wallabag
|
||||
|
||||

|
||||
[](https://github.com/wallabag/wallabag/actions/workflows/continuous-integration.yml?query=branch%3Amaster)
|
||||
[](https://matrix.to/#/#wallabag:matrix.org)
|
||||
[](https://liberapay.com/wallabag/donate)
|
||||
[](https://hosted.weblate.org/engage/wallabag/?utm_source=widget)
|
||||
|
||||
@ -70,7 +70,7 @@
|
||||
"doctrine/orm": "^2.20.2",
|
||||
"doctrine/persistence": "^3.4",
|
||||
"egulias/email-validator": "^4.0.4",
|
||||
"enshrined/svg-sanitize": "^0.21",
|
||||
"enshrined/svg-sanitize": "^0.22",
|
||||
"friendsofsymfony/jsrouting-bundle": "^3.5",
|
||||
"friendsofsymfony/oauth-server-bundle": "dev-master#dc8ff343363cf794d30eb1a123610d186a43f162",
|
||||
"friendsofsymfony/rest-bundle": "^3.8",
|
||||
|
||||
138
composer.lock
generated
138
composer.lock
generated
@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "8a12584ee6ea6887963779b321b4860e",
|
||||
"content-hash": "58a7696a7e938ab8a69c7faf2444cd09",
|
||||
"packages": [
|
||||
{
|
||||
"name": "babdev/pagerfanta-bundle",
|
||||
@ -1893,16 +1893,16 @@
|
||||
},
|
||||
{
|
||||
"name": "enshrined/svg-sanitize",
|
||||
"version": "0.21.0",
|
||||
"version": "0.22.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/darylldoyle/svg-sanitizer.git",
|
||||
"reference": "5e477468fac5c5ce933dce53af3e8e4e58dcccc9"
|
||||
"reference": "0afa95ea74be155a7bcd6c6fb60c276c39984500"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/darylldoyle/svg-sanitizer/zipball/5e477468fac5c5ce933dce53af3e8e4e58dcccc9",
|
||||
"reference": "5e477468fac5c5ce933dce53af3e8e4e58dcccc9",
|
||||
"url": "https://api.github.com/repos/darylldoyle/svg-sanitizer/zipball/0afa95ea74be155a7bcd6c6fb60c276c39984500",
|
||||
"reference": "0afa95ea74be155a7bcd6c6fb60c276c39984500",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -1932,9 +1932,9 @@
|
||||
"description": "An SVG sanitizer for PHP",
|
||||
"support": {
|
||||
"issues": "https://github.com/darylldoyle/svg-sanitizer/issues",
|
||||
"source": "https://github.com/darylldoyle/svg-sanitizer/tree/0.21.0"
|
||||
"source": "https://github.com/darylldoyle/svg-sanitizer/tree/0.22.0"
|
||||
},
|
||||
"time": "2025-01-13T09:32:25+00:00"
|
||||
"time": "2025-08-12T10:13:48+00:00"
|
||||
},
|
||||
{
|
||||
"name": "fossar/htmlawed",
|
||||
@ -4146,16 +4146,16 @@
|
||||
},
|
||||
{
|
||||
"name": "j0k3r/graby-site-config",
|
||||
"version": "1.0.200",
|
||||
"version": "1.0.202",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/j0k3r/graby-site-config.git",
|
||||
"reference": "e9cbbc776a7efcc9c06c07ff360744f266bbfc09"
|
||||
"reference": "870ff8f1f35cdbf87ff000b68c7bef5e712fd533"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/j0k3r/graby-site-config/zipball/e9cbbc776a7efcc9c06c07ff360744f266bbfc09",
|
||||
"reference": "e9cbbc776a7efcc9c06c07ff360744f266bbfc09",
|
||||
"url": "https://api.github.com/repos/j0k3r/graby-site-config/zipball/870ff8f1f35cdbf87ff000b68c7bef5e712fd533",
|
||||
"reference": "870ff8f1f35cdbf87ff000b68c7bef5e712fd533",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -4184,9 +4184,9 @@
|
||||
"description": "Graby site config files",
|
||||
"support": {
|
||||
"issues": "https://github.com/j0k3r/graby-site-config/issues",
|
||||
"source": "https://github.com/j0k3r/graby-site-config/tree/1.0.200"
|
||||
"source": "https://github.com/j0k3r/graby-site-config/tree/1.0.202"
|
||||
},
|
||||
"time": "2025-06-01T02:42:59+00:00"
|
||||
"time": "2025-08-01T07:03:24+00:00"
|
||||
},
|
||||
{
|
||||
"name": "j0k3r/httplug-ssrf-plugin",
|
||||
@ -4405,16 +4405,16 @@
|
||||
},
|
||||
{
|
||||
"name": "jean85/pretty-package-versions",
|
||||
"version": "2.1.0",
|
||||
"version": "2.1.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Jean85/pretty-package-versions.git",
|
||||
"reference": "3c4e5f62ba8d7de1734312e4fff32f67a8daaf10"
|
||||
"reference": "4d7aa5dab42e2a76d99559706022885de0e18e1a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/3c4e5f62ba8d7de1734312e4fff32f67a8daaf10",
|
||||
"reference": "3c4e5f62ba8d7de1734312e4fff32f67a8daaf10",
|
||||
"url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/4d7aa5dab42e2a76d99559706022885de0e18e1a",
|
||||
"reference": "4d7aa5dab42e2a76d99559706022885de0e18e1a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -4424,8 +4424,9 @@
|
||||
"require-dev": {
|
||||
"friendsofphp/php-cs-fixer": "^3.2",
|
||||
"jean85/composer-provided-replaced-stub-package": "^1.0",
|
||||
"phpstan/phpstan": "^1.4",
|
||||
"phpstan/phpstan": "^2.0",
|
||||
"phpunit/phpunit": "^7.5|^8.5|^9.6",
|
||||
"rector/rector": "^2.0",
|
||||
"vimeo/psalm": "^4.3 || ^5.0"
|
||||
},
|
||||
"type": "library",
|
||||
@ -4458,9 +4459,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/Jean85/pretty-package-versions/issues",
|
||||
"source": "https://github.com/Jean85/pretty-package-versions/tree/2.1.0"
|
||||
"source": "https://github.com/Jean85/pretty-package-versions/tree/2.1.1"
|
||||
},
|
||||
"time": "2024-11-18T16:19:46+00:00"
|
||||
"time": "2025-03-19T14:43:43+00:00"
|
||||
},
|
||||
{
|
||||
"name": "jms/metadata",
|
||||
@ -8049,16 +8050,16 @@
|
||||
},
|
||||
{
|
||||
"name": "sentry/sentry",
|
||||
"version": "4.10.0",
|
||||
"version": "4.14.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/getsentry/sentry-php.git",
|
||||
"reference": "2af937d47d8aadb8dab0b1d7b9557e495dd12856"
|
||||
"reference": "a28c4a6f5fda2bf730789a638501d7a737a64eda"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/getsentry/sentry-php/zipball/2af937d47d8aadb8dab0b1d7b9557e495dd12856",
|
||||
"reference": "2af937d47d8aadb8dab0b1d7b9557e495dd12856",
|
||||
"url": "https://api.github.com/repos/getsentry/sentry-php/zipball/a28c4a6f5fda2bf730789a638501d7a737a64eda",
|
||||
"reference": "a28c4a6f5fda2bf730789a638501d7a737a64eda",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -8122,7 +8123,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/getsentry/sentry-php/issues",
|
||||
"source": "https://github.com/getsentry/sentry-php/tree/4.10.0"
|
||||
"source": "https://github.com/getsentry/sentry-php/tree/4.14.1"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@ -8134,27 +8135,27 @@
|
||||
"type": "custom"
|
||||
}
|
||||
],
|
||||
"time": "2024-11-06T07:44:19+00:00"
|
||||
"time": "2025-06-23T15:25:52+00:00"
|
||||
},
|
||||
{
|
||||
"name": "sentry/sentry-symfony",
|
||||
"version": "5.2.0",
|
||||
"version": "5.3.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/getsentry/sentry-symfony.git",
|
||||
"reference": "394576244d8ac03fd2f305b82d23a6fd7a083b45"
|
||||
"reference": "5081e7d842424ec09a96e3c79c5b48b89d1195b2"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/getsentry/sentry-symfony/zipball/394576244d8ac03fd2f305b82d23a6fd7a083b45",
|
||||
"reference": "394576244d8ac03fd2f305b82d23a6fd7a083b45",
|
||||
"url": "https://api.github.com/repos/getsentry/sentry-symfony/zipball/5081e7d842424ec09a96e3c79c5b48b89d1195b2",
|
||||
"reference": "5081e7d842424ec09a96e3c79c5b48b89d1195b2",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"guzzlehttp/psr7": "^2.1.1",
|
||||
"jean85/pretty-package-versions": "^1.5||^2.0",
|
||||
"php": "^7.2||^8.0",
|
||||
"sentry/sentry": "^4.10.0",
|
||||
"sentry/sentry": "^4.14.1",
|
||||
"symfony/cache-contracts": "^1.1||^2.4||^3.0",
|
||||
"symfony/config": "^4.4.20||^5.0.11||^6.0||^7.0",
|
||||
"symfony/console": "^4.4.20||^5.0.11||^6.0||^7.0",
|
||||
@ -8224,7 +8225,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/getsentry/sentry-symfony/issues",
|
||||
"source": "https://github.com/getsentry/sentry-symfony/tree/5.2.0"
|
||||
"source": "https://github.com/getsentry/sentry-symfony/tree/5.3.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@ -8236,7 +8237,7 @@
|
||||
"type": "custom"
|
||||
}
|
||||
],
|
||||
"time": "2025-03-03T07:47:12+00:00"
|
||||
"time": "2025-07-07T14:14:08+00:00"
|
||||
},
|
||||
{
|
||||
"name": "simplepie/simplepie",
|
||||
@ -8815,16 +8816,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/cache-contracts",
|
||||
"version": "v3.5.1",
|
||||
"version": "v3.6.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/cache-contracts.git",
|
||||
"reference": "15a4f8e5cd3bce9aeafc882b1acab39ec8de2c1b"
|
||||
"reference": "5d68a57d66910405e5c0b63d6f0af941e66fc868"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/cache-contracts/zipball/15a4f8e5cd3bce9aeafc882b1acab39ec8de2c1b",
|
||||
"reference": "15a4f8e5cd3bce9aeafc882b1acab39ec8de2c1b",
|
||||
"url": "https://api.github.com/repos/symfony/cache-contracts/zipball/5d68a57d66910405e5c0b63d6f0af941e66fc868",
|
||||
"reference": "5d68a57d66910405e5c0b63d6f0af941e66fc868",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -8838,7 +8839,7 @@
|
||||
"name": "symfony/contracts"
|
||||
},
|
||||
"branch-alias": {
|
||||
"dev-main": "3.5-dev"
|
||||
"dev-main": "3.6-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
@ -8871,7 +8872,7 @@
|
||||
"standards"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/cache-contracts/tree/v3.5.1"
|
||||
"source": "https://github.com/symfony/cache-contracts/tree/v3.6.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@ -8887,7 +8888,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-09-25T14:20:29+00:00"
|
||||
"time": "2025-03-13T15:25:07+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/config",
|
||||
@ -15846,58 +15847,59 @@
|
||||
},
|
||||
{
|
||||
"name": "friendsofphp/php-cs-fixer",
|
||||
"version": "v3.75.0",
|
||||
"version": "v3.84.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git",
|
||||
"reference": "399a128ff2fdaf4281e4e79b755693286cdf325c"
|
||||
"reference": "38dad0767bf2a9b516b976852200ae722fe984ca"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/399a128ff2fdaf4281e4e79b755693286cdf325c",
|
||||
"reference": "399a128ff2fdaf4281e4e79b755693286cdf325c",
|
||||
"url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/38dad0767bf2a9b516b976852200ae722fe984ca",
|
||||
"reference": "38dad0767bf2a9b516b976852200ae722fe984ca",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"clue/ndjson-react": "^1.0",
|
||||
"composer/semver": "^3.4",
|
||||
"composer/xdebug-handler": "^3.0.3",
|
||||
"composer/xdebug-handler": "^3.0.5",
|
||||
"ext-filter": "*",
|
||||
"ext-hash": "*",
|
||||
"ext-json": "*",
|
||||
"ext-tokenizer": "*",
|
||||
"fidry/cpu-core-counter": "^1.2",
|
||||
"php": "^7.4 || ^8.0",
|
||||
"react/child-process": "^0.6.5",
|
||||
"react/child-process": "^0.6.6",
|
||||
"react/event-loop": "^1.0",
|
||||
"react/promise": "^2.0 || ^3.0",
|
||||
"react/promise": "^2.11 || ^3.0",
|
||||
"react/socket": "^1.0",
|
||||
"react/stream": "^1.0",
|
||||
"sebastian/diff": "^4.0 || ^5.1 || ^6.0 || ^7.0",
|
||||
"symfony/console": "^5.4 || ^6.4 || ^7.0",
|
||||
"symfony/event-dispatcher": "^5.4 || ^6.4 || ^7.0",
|
||||
"symfony/filesystem": "^5.4 || ^6.4 || ^7.0",
|
||||
"symfony/finder": "^5.4 || ^6.4 || ^7.0",
|
||||
"symfony/options-resolver": "^5.4 || ^6.4 || ^7.0",
|
||||
"symfony/polyfill-mbstring": "^1.31",
|
||||
"symfony/polyfill-php80": "^1.31",
|
||||
"symfony/polyfill-php81": "^1.31",
|
||||
"symfony/process": "^5.4 || ^6.4 || ^7.2",
|
||||
"symfony/stopwatch": "^5.4 || ^6.4 || ^7.0"
|
||||
"sebastian/diff": "^4.0.6 || ^5.1.1 || ^6.0.2 || ^7.0",
|
||||
"symfony/console": "^5.4.45 || ^6.4.13 || ^7.0",
|
||||
"symfony/event-dispatcher": "^5.4.45 || ^6.4.13 || ^7.0",
|
||||
"symfony/filesystem": "^5.4.45 || ^6.4.13 || ^7.0",
|
||||
"symfony/finder": "^5.4.45 || ^6.4.17 || ^7.0",
|
||||
"symfony/options-resolver": "^5.4.45 || ^6.4.16 || ^7.0",
|
||||
"symfony/polyfill-mbstring": "^1.32",
|
||||
"symfony/polyfill-php80": "^1.32",
|
||||
"symfony/polyfill-php81": "^1.32",
|
||||
"symfony/process": "^5.4.47 || ^6.4.20 || ^7.2",
|
||||
"symfony/stopwatch": "^5.4.45 || ^6.4.19 || ^7.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"facile-it/paraunit": "^1.3.1 || ^2.6",
|
||||
"infection/infection": "^0.29.14",
|
||||
"justinrainbow/json-schema": "^5.3 || ^6.2",
|
||||
"keradus/cli-executor": "^2.1",
|
||||
"justinrainbow/json-schema": "^5.3 || ^6.4",
|
||||
"keradus/cli-executor": "^2.2",
|
||||
"mikey179/vfsstream": "^1.6.12",
|
||||
"php-coveralls/php-coveralls": "^2.7",
|
||||
"php-coveralls/php-coveralls": "^2.8",
|
||||
"php-cs-fixer/accessible-object": "^1.1",
|
||||
"php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.6",
|
||||
"php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.6",
|
||||
"phpunit/phpunit": "^9.6.22 || ^10.5.45 || ^11.5.12",
|
||||
"symfony/var-dumper": "^5.4.48 || ^6.4.18 || ^7.2.3",
|
||||
"symfony/yaml": "^5.4.45 || ^6.4.18 || ^7.2.3"
|
||||
"phpunit/phpunit": "^9.6.23 || ^10.5.47 || ^11.5.25",
|
||||
"symfony/polyfill-php84": "^1.32",
|
||||
"symfony/var-dumper": "^5.4.48 || ^6.4.23 || ^7.3.1",
|
||||
"symfony/yaml": "^5.4.45 || ^6.4.23 || ^7.3.1"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-dom": "For handling output formats in XML",
|
||||
@ -15938,7 +15940,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues",
|
||||
"source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.75.0"
|
||||
"source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.84.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@ -15946,7 +15948,7 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2025-03-31T18:40:42+00:00"
|
||||
"time": "2025-07-15T18:21:57+00:00"
|
||||
},
|
||||
{
|
||||
"name": "friendsoftwig/twigcs",
|
||||
@ -19453,6 +19455,6 @@
|
||||
"ext-tokenizer": "*",
|
||||
"ext-xml": "*"
|
||||
},
|
||||
"platform-dev": [],
|
||||
"plugin-api-version": "2.3.0"
|
||||
"platform-dev": {},
|
||||
"plugin-api-version": "2.6.0"
|
||||
}
|
||||
|
||||
47
migrations/Version20250413133131.php
Normal file
47
migrations/Version20250413133131.php
Normal file
@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Application\Migrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Wallabag\Doctrine\WallabagMigration;
|
||||
|
||||
/**
|
||||
* Add boolean for two-step setup for google authenticator.
|
||||
*/
|
||||
final class Version20250413133131 extends WallabagMigration
|
||||
{
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$userTable = $schema->getTable($this->getTable('user'));
|
||||
|
||||
$this->skipIf($userTable->hasColumn('google_authenticator'), 'It seems that you already played this migration.');
|
||||
|
||||
$userTable->addColumn('google_authenticator', 'boolean', [
|
||||
'default' => false,
|
||||
'notnull' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Query to update data in user table, as it's not possible to perform this in the `up` method.
|
||||
*/
|
||||
public function postUp(Schema $schema): void
|
||||
{
|
||||
$this->skipIf(!$schema->getTable($this->getTable('user'))->hasColumn('google_authenticator'), 'Unable to update google_authenticator column');
|
||||
$this->connection->executeQuery(
|
||||
'UPDATE ' . $this->getTable('user') . ' SET google_authenticator = :googleAuthenticator WHERE googleAuthenticatorSecret IS NOT NULL AND googleAuthenticatorSecret <> :emptyString',
|
||||
[
|
||||
'googleAuthenticator' => true,
|
||||
'emptyString' => '',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$userTable = $schema->getTable($this->getTable('user'));
|
||||
$userTable->dropColumn('google_authenticator');
|
||||
}
|
||||
}
|
||||
14
package.json
14
package.json
@ -41,13 +41,13 @@
|
||||
"url": "https://github.com/wallabag/wallabag/issues"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.27.7",
|
||||
"@babel/eslint-parser": "^7.27.5",
|
||||
"@babel/preset-env": "^7.27.2",
|
||||
"@babel/core": "^7.28.3",
|
||||
"@babel/eslint-parser": "^7.28.0",
|
||||
"@babel/preset-env": "^7.28.3",
|
||||
"@symfony/webpack-encore": "^5.1.0",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"babel-loader": "^10.0.0",
|
||||
"core-js": "^3.43.0",
|
||||
"core-js": "^3.45.0",
|
||||
"css-loader": "^7.1.2",
|
||||
"eslint": "^8.57.1",
|
||||
"eslint-config-airbnb-base": "^15.0.0",
|
||||
@ -55,12 +55,12 @@
|
||||
"eslint-webpack-plugin": "^5.0.2",
|
||||
"file-loader": "^6.2.0",
|
||||
"lato-font": "^3.0.0",
|
||||
"mini-css-extract-plugin": "^2.9.2",
|
||||
"mini-css-extract-plugin": "^2.9.4",
|
||||
"postcss": "^8.5.6",
|
||||
"postcss-loader": "^8.1.1",
|
||||
"postcss-scss": "^4.0.9",
|
||||
"regenerator-runtime": "^0.14.1",
|
||||
"sass-embedded": "^1.89.2",
|
||||
"sass-embedded": "^1.90.0",
|
||||
"sass-loader": "^16.0.5",
|
||||
"style-loader": "^4.0.0",
|
||||
"stylelint": "^15.11.0",
|
||||
@ -69,7 +69,7 @@
|
||||
"stylelint-scss": "^5.3.2",
|
||||
"terser-webpack-plugin": "^5.3.14",
|
||||
"url-loader": "^4.1.1",
|
||||
"webpack": "^5.99.9",
|
||||
"webpack": "^5.101.2",
|
||||
"webpack-cli": "^5.1.4",
|
||||
"webpack-manifest-plugin": "^5.0.1",
|
||||
"webpack-merge": "^6.0.1",
|
||||
|
||||
@ -290,6 +290,17 @@ class EntryRestController extends WallabagRestController
|
||||
* example="200",
|
||||
* )
|
||||
* ),
|
||||
* @OA\Parameter(
|
||||
* name="annotations",
|
||||
* in="query",
|
||||
* description="filter entries with annotations. all entries by default",
|
||||
* required=false,
|
||||
* @OA\Schema(
|
||||
* type="integer",
|
||||
* enum={"1", "0"},
|
||||
* default="0"
|
||||
* )
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response="200",
|
||||
* description="Returned when successful"
|
||||
@ -315,6 +326,7 @@ class EntryRestController extends WallabagRestController
|
||||
$detail = strtolower($request->query->get('detail', 'full'));
|
||||
$domainName = (null === $request->query->get('domain_name')) ? '' : (string) $request->query->get('domain_name');
|
||||
$httpStatus = (!\array_key_exists((int) $request->query->get('http_status'), Response::$statusTexts)) ? null : (int) $request->query->get('http_status');
|
||||
$hasAnnotations = (null === $request->query->get('annotations')) ? null : (bool) $request->query->get('annotations');
|
||||
|
||||
try {
|
||||
/** @var Pagerfanta $pager */
|
||||
@ -330,7 +342,8 @@ class EntryRestController extends WallabagRestController
|
||||
$detail,
|
||||
$domainName,
|
||||
$isNotParsed,
|
||||
$httpStatus
|
||||
$httpStatus,
|
||||
$hasAnnotations
|
||||
);
|
||||
} catch (\Exception $e) {
|
||||
throw new BadRequestHttpException($e->getMessage());
|
||||
@ -356,6 +369,7 @@ class EntryRestController extends WallabagRestController
|
||||
'tags' => $tags,
|
||||
'since' => $since,
|
||||
'detail' => $detail,
|
||||
'annotations' => $hasAnnotations,
|
||||
],
|
||||
true
|
||||
)
|
||||
|
||||
@ -313,6 +313,7 @@ class ConfigController extends AbstractController
|
||||
$user = $this->getUser();
|
||||
|
||||
$user->setGoogleAuthenticatorSecret('');
|
||||
$user->setGoogleAuthenticator(false);
|
||||
$user->setBackupCodes(null);
|
||||
|
||||
$this->userManager->updateUser($user);
|
||||
@ -354,11 +355,6 @@ class ConfigController extends AbstractController
|
||||
$this->userManager->updateUser($user);
|
||||
$this->entityManager->flush();
|
||||
|
||||
$this->addFlash(
|
||||
'notice',
|
||||
'flashes.config.notice.otp_enabled'
|
||||
);
|
||||
|
||||
return $this->render('Config/otp_app.html.twig', [
|
||||
'backupCodes' => $backupCodes,
|
||||
'qr_code' => $googleAuthenticator->getQRContent($user),
|
||||
@ -408,6 +404,9 @@ class ConfigController extends AbstractController
|
||||
'notice',
|
||||
'flashes.config.notice.otp_enabled'
|
||||
);
|
||||
$user->setGoogleAuthenticator(true);
|
||||
$this->userManager->updateUser($user);
|
||||
$this->entityManager->flush();
|
||||
|
||||
return $this->redirect($this->generateUrl('config') . '#set3');
|
||||
}
|
||||
@ -421,8 +420,9 @@ class ConfigController extends AbstractController
|
||||
$user->setBackupCodes(null);
|
||||
|
||||
$this->userManager->updateUser($user);
|
||||
$this->entityManager->flush();
|
||||
|
||||
return $this->redirect($this->generateUrl('config') . '#set3');
|
||||
return $this->redirect($this->generateUrl('config_otp_app'), 307);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -627,6 +627,16 @@ class EntryController extends AbstractController
|
||||
$currentEntryId = $request->attributes->getInt('id');
|
||||
|
||||
$formOptions = [];
|
||||
$direction = 'desc';
|
||||
$sortBy = null;
|
||||
|
||||
if (null !== $request->get('entry_filter') && null !== $request->get('entry_filter')['sortType'] && '' !== $request->get('entry_filter')['sortType']) {
|
||||
$direction = (null !== $request->get('entry_filter')['sortOrder'] && \in_array($request->get('entry_filter')['sortOrder'], ['asc', 'desc'], true)) ? $request->get('entry_filter')['sortOrder'] : 'desc';
|
||||
|
||||
if (\in_array($request->get('entry_filter')['sortType'], ['id','title','createdAt', 'url', 'readingTime'], true)) {
|
||||
$sortBy = $request->get('entry_filter')['sortType'];
|
||||
}
|
||||
}
|
||||
|
||||
switch ($type) {
|
||||
case 'search':
|
||||
@ -654,7 +664,7 @@ class EntryController extends AbstractController
|
||||
$qb = $this->entryRepository->getBuilderForSameDomainByUser($this->getUser()->getId(), $currentEntryId);
|
||||
break;
|
||||
case 'all':
|
||||
$qb = $this->entryRepository->getBuilderForAllByUser($this->getUser()->getId());
|
||||
$qb = $this->entryRepository->getBuilderForAllByUser($this->getUser()->getId(), $sortBy, $direction);
|
||||
break;
|
||||
default:
|
||||
throw new \InvalidArgumentException(\sprintf('Type "%s" is not implemented.', $type));
|
||||
|
||||
@ -8,10 +8,12 @@ use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
use Wallabag\Controller\AbstractController;
|
||||
use Wallabag\Form\Type\UploadImportType;
|
||||
use Wallabag\Import\PocketCsvImport;
|
||||
use Wallabag\Redis\Producer as RedisProducer;
|
||||
|
||||
class PocketCsvController extends HtmlController
|
||||
class PocketCsvController extends AbstractController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly PocketCsvImport $pocketCsvImport,
|
||||
@ -25,7 +27,58 @@ class PocketCsvController extends HtmlController
|
||||
#[IsGranted('IMPORT_ENTRIES')]
|
||||
public function indexAction(Request $request, TranslatorInterface $translator)
|
||||
{
|
||||
return parent::indexAction($request, $translator);
|
||||
$form = $this->createForm(UploadImportType::class);
|
||||
$form->handleRequest($request);
|
||||
|
||||
$this->pocketCsvImport->setUser($this->getUser());
|
||||
|
||||
if ($this->craueConfig->get('import_with_rabbitmq')) {
|
||||
$this->pocketCsvImport->setProducer($this->rabbitMqProducer);
|
||||
} elseif ($this->craueConfig->get('import_with_redis')) {
|
||||
$this->pocketCsvImport->setProducer($this->redisProducer);
|
||||
}
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$file = $form->get('file')->getData();
|
||||
$markAsRead = $form->get('mark_as_read')->getData();
|
||||
$name = 'pocket_' . $this->getUser()->getId() . '.csv';
|
||||
|
||||
if (null !== $file && \in_array($file->getClientMimeType(), $this->getParameter('wallabag.allow_mimetypes'), true) && $file->move($this->getParameter('wallabag.resource_dir'), $name)) {
|
||||
$res = $this->pocketCsvImport
|
||||
->setFilepath($this->getParameter('wallabag.resource_dir') . '/' . $name)
|
||||
->setMarkAsRead($markAsRead)
|
||||
->import();
|
||||
|
||||
$message = 'flashes.import.notice.failed';
|
||||
|
||||
if (true === $res) {
|
||||
$summary = $this->pocketCsvImport->getSummary();
|
||||
$message = $translator->trans('flashes.import.notice.summary', [
|
||||
'%imported%' => $summary['imported'],
|
||||
'%skipped%' => $summary['skipped'],
|
||||
]);
|
||||
|
||||
if (0 < $summary['queued']) {
|
||||
$message = $translator->trans('flashes.import.notice.summary_with_queue', [
|
||||
'%queued%' => $summary['queued'],
|
||||
]);
|
||||
}
|
||||
|
||||
unlink($this->getParameter('wallabag.resource_dir') . '/' . $name);
|
||||
}
|
||||
|
||||
$this->addFlash('notice', $message);
|
||||
|
||||
return $this->redirect($this->generateUrl('homepage'));
|
||||
}
|
||||
|
||||
$this->addFlash('notice', 'flashes.import.notice.failed_on_file');
|
||||
}
|
||||
|
||||
return $this->render('Import/PocketCsv/index.html.twig', [
|
||||
'form' => $form->createView(),
|
||||
'import' => $this->pocketCsvImport,
|
||||
]);
|
||||
}
|
||||
|
||||
protected function getImportService()
|
||||
|
||||
@ -147,6 +147,11 @@ class User extends BaseUser implements EmailTwoFactorInterface, GoogleTwoFactorI
|
||||
#[ORM\Column(name: 'googleAuthenticatorSecret', type: 'string', nullable: true)]
|
||||
private $googleAuthenticatorSecret;
|
||||
|
||||
// default value is explicitly set to false here to ensure that Doctrine
|
||||
// does not complain about schema mapping mismatch
|
||||
#[ORM\Column(name: 'google_authenticator', type: 'boolean', options: ['default' => false])]
|
||||
private $googleAuthenticator = false;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
@ -264,6 +269,11 @@ class User extends BaseUser implements EmailTwoFactorInterface, GoogleTwoFactorI
|
||||
$this->emailTwoFactor = $emailTwoFactor;
|
||||
}
|
||||
|
||||
public function setGoogleAuthenticator(bool $googleAuthenticator): void
|
||||
{
|
||||
$this->googleAuthenticator = $googleAuthenticator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used in the user config form to be "like" the email option.
|
||||
*/
|
||||
@ -294,7 +304,7 @@ class User extends BaseUser implements EmailTwoFactorInterface, GoogleTwoFactorI
|
||||
|
||||
public function isGoogleAuthenticatorEnabled(): bool
|
||||
{
|
||||
return $this->googleAuthenticatorSecret ? true : false;
|
||||
return $this->googleAuthenticator;
|
||||
}
|
||||
|
||||
public function getGoogleAuthenticatorUsername(): string
|
||||
|
||||
@ -205,6 +205,24 @@ class EntryFilterType extends AbstractType
|
||||
'choices' => array_flip($this->repository->findDistinctLanguageByUser($user->getId())),
|
||||
'label' => 'entry.filters.language_label',
|
||||
])
|
||||
->add('sortOrder', ChoiceFilterType::class, [
|
||||
'apply_filter' => function (QueryInterface $filterQuery, $field, $values) { },
|
||||
'choices' => [
|
||||
'entry.sort.ascending' => 'asc',
|
||||
'entry.sort.descending' => 'desc',
|
||||
],
|
||||
'label' => 'entry.sort.order_label',
|
||||
])
|
||||
->add('sortType', ChoiceFilterType::class, [
|
||||
'apply_filter' => function (QueryInterface $filterQuery, $field, $values) { },
|
||||
'choices' => [
|
||||
'entry.sort.by.creation_date' => 'createdAt',
|
||||
'entry.sort.by.title' => 'title',
|
||||
'entry.sort.by.url' => 'url',
|
||||
'entry.sort.by.reading_time' => 'readingTime',
|
||||
],
|
||||
'label' => 'entry.sort.status_label',
|
||||
])
|
||||
;
|
||||
}
|
||||
|
||||
|
||||
@ -30,10 +30,11 @@ class EntryRepository extends ServiceEntityRepository
|
||||
*
|
||||
* @return QueryBuilder
|
||||
*/
|
||||
public function getBuilderForAllByUser($userId)
|
||||
public function getBuilderForAllByUser($userId, $sortBy = 'createdAt', $direction = 'desc')
|
||||
{
|
||||
$sortBy = $sortBy ?: 'createdAt';
|
||||
return $this
|
||||
->getSortedQueryBuilderByUser($userId)
|
||||
->getSortedQueryBuilderByUser($userId, $sortBy, $direction)
|
||||
;
|
||||
}
|
||||
|
||||
@ -268,16 +269,17 @@ class EntryRepository extends ServiceEntityRepository
|
||||
* @param string $order
|
||||
* @param int $since
|
||||
* @param string $tags
|
||||
* @param string $detail 'metadata' or 'full'. Include content field if 'full'
|
||||
* @param string $detail 'metadata' or 'full'. Include content field if 'full'
|
||||
* @param string $domainName
|
||||
* @param int $httpStatus
|
||||
* @param bool $isNotParsed
|
||||
* @param bool $hasAnnotations
|
||||
*
|
||||
* @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 = '', $detail = 'full', $domainName = '', $isNotParsed = null, $httpStatus = null)
|
||||
public function findEntries($userId, $isArchived = null, $isStarred = null, $isPublic = null, $sort = 'created', $order = 'asc', $since = 0, $tags = '', $detail = 'full', $domainName = '', $isNotParsed = null, $httpStatus = null, $hasAnnotations = null)
|
||||
{
|
||||
if (!\in_array(strtolower($detail), ['full', 'metadata'], true)) {
|
||||
throw new \Exception('Detail "' . $detail . '" parameter is wrong, allowed: full or metadata');
|
||||
@ -342,6 +344,16 @@ class EntryRepository extends ServiceEntityRepository
|
||||
$qb->andWhere('e.domainName = :domainName')->setParameter('domainName', $domainName);
|
||||
}
|
||||
|
||||
if (null !== $hasAnnotations) {
|
||||
if ($hasAnnotations) {
|
||||
$qb->leftJoin('e.annotations', 'a')
|
||||
->andWhere('a.id IS NOT NULL');
|
||||
} else {
|
||||
$qb->leftJoin('e.annotations', 'a')
|
||||
->andWhere('a.id IS NULL');
|
||||
}
|
||||
}
|
||||
|
||||
if (!\in_array(strtolower($order), ['asc', 'desc'], true)) {
|
||||
throw new \Exception('Order "' . $order . '" parameter is wrong, allowed: asc or desc');
|
||||
}
|
||||
|
||||
@ -18,14 +18,14 @@
|
||||
<img data-controller="qrcode" data-qrcode-url-value="{{ qr_code|raw }}" class="hide-on-med-and-down" />
|
||||
</p>
|
||||
|
||||
<div data-controller="highlight">
|
||||
<div id="config_otp_app_secret">
|
||||
{{ 'config.otp.app.two_factor_code_description_5'|trans }} <pre><code>{{ secret }}</code></pre>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<p>{{ 'config.otp.app.two_factor_code_description_3'|trans }}</p>
|
||||
|
||||
<div data-controller="highlight"><pre><code>{{ backupCodes|join("\n") }}</code></pre></div>
|
||||
<div><pre><code>{{ backupCodes|join("\n") }}</code></pre></div>
|
||||
</li>
|
||||
<li>
|
||||
<p>{{ 'config.otp.app.two_factor_code_description_4'|trans }}</p>
|
||||
@ -36,7 +36,7 @@
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<form class="form" action="{{ path("config_otp_app_check") }}" method="post">
|
||||
<form class="form" action="{{ path("config_otp_app_check") }}" method="post" name="config_otp_app_check">
|
||||
<input type="hidden" name="token" value="{{ csrf_token('otp') }}" />
|
||||
<div class="card-content">
|
||||
<div class="row">
|
||||
|
||||
@ -21,7 +21,7 @@
|
||||
<input type="hidden" name="token" value="{{ csrf_token('archive-entry') }}"/>
|
||||
|
||||
<button type="submit" class="btn-link tool grey-text" title="{{ 'entry.list.toogle_as_read'|trans }}">
|
||||
<i class="material-icons">{% if entry.isArchived == 0 %}archive{% else %}unarchive{% endif %}</i>
|
||||
<i class="material-icons">{% if entry.isArchived == 0 %}done{% else %}unarchive{% endif %}</i>
|
||||
</button>
|
||||
</form>
|
||||
</li>
|
||||
|
||||
@ -26,7 +26,7 @@
|
||||
<input type="hidden" name="token" value="{{ csrf_token('archive-entry') }}"/>
|
||||
|
||||
<button type="submit" class="btn-link tool grey-text" title="{{ 'entry.list.toogle_as_read'|trans }}">
|
||||
<i class="material-icons">{% if entry.isArchived == 0 %}archive{% else %}unarchive{% endif %}</i>
|
||||
<i class="material-icons">{% if entry.isArchived == 0 %}done{% else %}unarchive{% endif %}</i>
|
||||
</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
|
||||
@ -93,7 +93,7 @@
|
||||
<div class="mass-action">
|
||||
<div class="mass-action-group">
|
||||
<input type="checkbox" form="form_mass_action" class="entry-checkbox-input" data-action="batch-edit#toggleSelection" />
|
||||
<button class="mass-action-button btn cyan darken-1" type="submit" form="form_mass_action" name="toggle-read" title="{{ 'entry.list.toogle_as_read'|trans }}"><i class="material-icons">archive</i></button>
|
||||
<button class="mass-action-button btn cyan darken-1" type="submit" form="form_mass_action" name="toggle-read" title="{{ 'entry.list.toogle_as_read'|trans }}"><i class="material-icons">done</i></button>
|
||||
<button class="mass-action-button btn cyan darken-1" type="submit" form="form_mass_action" name="toggle-star" title="{{ 'entry.list.toogle_as_star'|trans }}" ><i class="material-icons">star</i></button>
|
||||
<button class="mass-action-button btn cyan darken-1" type="submit" form="form_mass_action" name="delete" onclick="return confirm('{{ 'entry.confirm.delete_entries'|trans|escape('js') }}')" title="{{ 'entry.list.delete'|trans }}"><i class="material-icons">delete</i></button>
|
||||
</div>
|
||||
@ -276,6 +276,22 @@
|
||||
<label for="entry_filter_createdAt_right_date" class="active">{{ 'entry.filters.created_at.to'|trans }}</label>
|
||||
</div>
|
||||
|
||||
<div class="col s6">
|
||||
{{ form_label(form.sortType) }}
|
||||
</div>
|
||||
|
||||
<div class="col s6">
|
||||
{{ form_label(form.sortOrder) }}
|
||||
</div>
|
||||
|
||||
<div class="col s6">
|
||||
{{ form_widget(form.sortType) }}
|
||||
</div>
|
||||
|
||||
<div class="col s6">
|
||||
{{ form_widget(form.sortOrder) }}
|
||||
</div>
|
||||
|
||||
<div class="col s6">
|
||||
<button type="reset" class="center waves-effect waves-green btn-flat">{{ 'entry.filters.action.clear'|trans }}</button>
|
||||
</div>
|
||||
|
||||
@ -31,7 +31,7 @@
|
||||
<input type="hidden" name="token" value="{{ csrf_token('archive-entry') }}"/>
|
||||
|
||||
<button type="submit" class="waves-effect" title="{{ 'entry.view.left_menu.set_as_read'|trans }}">
|
||||
<i class="material-icons small">{% if entry.isArchived == 0 %}archive{% else %}unarchive{% endif %}</i>
|
||||
<i class="material-icons small">{% if entry.isArchived == 0 %}done{% else %}unarchive{% endif %}</i>
|
||||
</button>
|
||||
</form>
|
||||
</li>
|
||||
@ -92,7 +92,7 @@
|
||||
<input type="hidden" name="token" value="{{ csrf_token('archive-entry') }}"/>
|
||||
|
||||
<button type="submit" class="waves-effect collapsible-header markasread" title="{{ mark_as_read_label|trans }}" data-shortcuts-target="markAsRead">
|
||||
<i class="material-icons small">{% if entry.isArchived == 0 %}archive{% else %}unarchive{% endif %}</i>
|
||||
<i class="material-icons small">{% if entry.isArchived == 0 %}done{% else %}unarchive{% endif %}</i>
|
||||
<span>{{ mark_as_read_label|trans }}</span>
|
||||
</button>
|
||||
</form>
|
||||
@ -381,7 +381,7 @@
|
||||
<input type="hidden" name="token" value="{{ csrf_token('archive-entry') }}"/>
|
||||
|
||||
<button type="submit" class="btn-floating">
|
||||
<i class="material-icons">{% if entry.isArchived == 0 %}archive{% else %}unarchive{% endif %}</i>
|
||||
<i class="material-icons">{% if entry.isArchived == 0 %}done{% else %}unarchive{% endif %}</i>
|
||||
</button>
|
||||
</form>
|
||||
</li>
|
||||
|
||||
@ -20,10 +20,11 @@
|
||||
|
||||
<div id="set1" class="col s12">
|
||||
<dt>{{ 'about.who_behind_wallabag.developped_by'|trans }}</dt>
|
||||
<dd><a href="mailto:nicolas@loeuillet.org">Nicolas Lœuillet</a> — <a href="https://nicolas.loeuillet.org">{{ 'about.who_behind_wallabag.website'|trans }}</a></dd>
|
||||
<dd>Nicolas Lœuillet — <a href="https://nicolas.loeuillet.org">{{ 'about.who_behind_wallabag.website'|trans }}</a></dd>
|
||||
<dd>Thomas Citharel — <a href="https://tcit.fr">{{ 'about.who_behind_wallabag.website'|trans }}</a></dd>
|
||||
<dd>Jérémy Benoist — <a href="https://www.j0k3r.net">{{ 'about.who_behind_wallabag.website'|trans }}</a></dd>
|
||||
<dd>Kevin Decherf — <a href="https://kdecherf.com/">{{ 'about.who_behind_wallabag.website'|trans }}</a></dd>
|
||||
<dd>Yassine Guedidi</dd>
|
||||
<dt>{{ 'about.who_behind_wallabag.many_contributors'|trans|raw }}</dt>
|
||||
<dt>{{ 'about.who_behind_wallabag.project_website'|trans }}</dt>
|
||||
<dd><a href="https://www.wallabag.org">https://www.wallabag.org</a></dd>
|
||||
@ -33,11 +34,7 @@
|
||||
|
||||
<div id="set2" class="col s12">
|
||||
<dl>
|
||||
<dt>{{ 'about.getting_help.documentation'|trans }}</dt>
|
||||
<dd><a href="https://doc.wallabag.org/en/">english</a></dd>
|
||||
<dd><a href="https://doc.wallabag.org/fr/">français</a></dd>
|
||||
<dd><a href="https://doc.wallabag.org/de/">deutsch</a></dd>
|
||||
<dd><a href="https://doc.wallabag.org/it/">italiano</a></dd>
|
||||
<dt><a href="https://doc.wallabag.org/">{{ 'about.getting_help.documentation'|trans }}</a></dt>
|
||||
|
||||
<dt>{{ 'about.getting_help.bug_reports'|trans }}</dt>
|
||||
<dd>{{ 'about.getting_help.support'|trans|raw }}</dd>
|
||||
|
||||
@ -56,7 +56,7 @@
|
||||
</div>
|
||||
<div class="card-action">
|
||||
<ul>
|
||||
<li><a href="{{ path('import_pocket') }}">{{ 'quickstart.migrate.pocket'|trans }}</a></li>
|
||||
<li><a href="{{ path('import_pocket_csv') }}">{{ 'quickstart.migrate.pocket'|trans }}</a></li>
|
||||
<li><a href="{{ path('import_readability') }}">{{ 'quickstart.migrate.readability'|trans }}</a></li>
|
||||
<li><a href="{{ path('import_instapaper') }}">{{ 'quickstart.migrate.instapaper'|trans }}</a></li>
|
||||
<li><a href="{{ path('import') }}">{{ 'quickstart.more'|trans }}</a></li>
|
||||
@ -75,7 +75,6 @@
|
||||
<div class="card-action">
|
||||
<ul>
|
||||
<li><a href="{{ path('developer') }}">{{ 'quickstart.developer.create_application'|trans }}</a></li>
|
||||
<li><a href="https://doc.wallabag.org/en/developer/docker.html">{{ 'quickstart.developer.use_docker'|trans }}</a></li>
|
||||
<li><a href="https://doc.wallabag.org/">{{ 'quickstart.more'|trans }}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@ -27,7 +27,7 @@
|
||||
<form class="card-tag-form hidden" data-tag-target="form" action="{{ path('tag_rename', {'slug': tag.slug, redirect: current_path}) }}" method="POST">
|
||||
{{ form_widget(renameForms[tag.id].label, {'attr': {'value': tag.label, 'data-tag-target': 'input'}}) }}
|
||||
{{ form_rest(renameForms[tag.id]) }}
|
||||
<button type="submit"><i class="material-icons">archive</i></button>
|
||||
<button type="submit"><i class="material-icons">done</i></button>
|
||||
</form>
|
||||
<button type="button" data-tag-target="edit" data-action="tag#showForm">
|
||||
<i class="material-icons">mode_edit</i>
|
||||
|
||||
@ -227,6 +227,7 @@ class EntryRestControllerTest extends WallabagApiTestCase
|
||||
'public' => 0,
|
||||
'notParsed' => 0,
|
||||
'http_status' => 200,
|
||||
'annotations' => 1,
|
||||
]);
|
||||
|
||||
$this->assertSame(200, $this->client->getResponse()->getStatusCode());
|
||||
@ -254,6 +255,7 @@ class EntryRestControllerTest extends WallabagApiTestCase
|
||||
$this->assertStringContainsString('tags=foo', $content['_links'][$link]['href']);
|
||||
$this->assertStringContainsString('since=1443274283', $content['_links'][$link]['href']);
|
||||
$this->assertStringContainsString('public=0', $content['_links'][$link]['href']);
|
||||
$this->assertStringContainsString('annotations=1', $content['_links'][$link]['href']);
|
||||
}
|
||||
|
||||
$this->assertSame('application/json', $this->client->getResponse()->headers->get('Content-Type'));
|
||||
@ -305,6 +307,86 @@ class EntryRestControllerTest extends WallabagApiTestCase
|
||||
$this->assertSame('application/json', $this->client->getResponse()->headers->get('Content-Type'));
|
||||
}
|
||||
|
||||
public function testGetEntriesWithAnnotationsFilter()
|
||||
{
|
||||
// Test filter for entries WITH annotations
|
||||
// From fixtures: entry1 and entry2 have annotations, entry4, entry5, entry6, entry7 don't
|
||||
$this->client->request('GET', '/api/entries', [
|
||||
'annotations' => 1,
|
||||
]);
|
||||
|
||||
$this->assertSame(200, $this->client->getResponse()->getStatusCode());
|
||||
|
||||
$content = json_decode($this->client->getResponse()->getContent(), true);
|
||||
|
||||
$this->assertArrayHasKey('items', $content['_embedded']);
|
||||
|
||||
// Check that only entries with annotations are returned
|
||||
$entriesWithAnnotations = ['http://0.0.0.0/entry1', 'http://0.0.0.0/entry2'];
|
||||
$entriesWithoutAnnotations = ['http://0.0.0.0/entry4', 'http://0.0.0.0/entry5', 'http://0.0.0.0/entry6', 'http://0.0.0.0/entry7'];
|
||||
|
||||
foreach ($content['_embedded']['items'] as $item) {
|
||||
if (\in_array($item['url'], $entriesWithAnnotations, true)) {
|
||||
$this->assertNotEmpty($item['annotations'], 'Entry with URL ' . $item['url'] . ' should have annotations');
|
||||
}
|
||||
$this->assertNotContains($item['url'], $entriesWithoutAnnotations, 'Entry without annotations should NOT be in the results');
|
||||
}
|
||||
|
||||
// Ensure we have at least the entries with annotations
|
||||
$foundUrls = array_column($content['_embedded']['items'], 'url');
|
||||
$this->assertContains('http://0.0.0.0/entry1', $foundUrls, 'entry1 with annotations should be in the results');
|
||||
$this->assertContains('http://0.0.0.0/entry2', $foundUrls, 'entry2 with annotations should be in the results');
|
||||
|
||||
// Check pagination links contain the filter
|
||||
$this->assertArrayHasKey('_links', $content);
|
||||
foreach (['self', 'first', 'last'] as $link) {
|
||||
$this->assertArrayHasKey('href', $content['_links'][$link]);
|
||||
$this->assertStringContainsString('annotations=1', $content['_links'][$link]['href']);
|
||||
}
|
||||
|
||||
$this->assertSame('application/json', $this->client->getResponse()->headers->get('Content-Type'));
|
||||
}
|
||||
|
||||
public function testGetEntriesWithoutAnnotationsFilter()
|
||||
{
|
||||
// Test filter for entries WITHOUT annotations
|
||||
// From fixtures: entry1 and entry2 have annotations, entry4, entry5, entry6, entry7 don't
|
||||
$this->client->request('GET', '/api/entries', [
|
||||
'annotations' => 0,
|
||||
]);
|
||||
|
||||
$this->assertSame(200, $this->client->getResponse()->getStatusCode());
|
||||
|
||||
$content = json_decode($this->client->getResponse()->getContent(), true);
|
||||
|
||||
$this->assertArrayHasKey('items', $content['_embedded']);
|
||||
|
||||
// Check that only entries without annotations are returned
|
||||
$entriesWithAnnotations = ['http://0.0.0.0/entry1', 'http://0.0.0.0/entry2'];
|
||||
$entriesWithoutAnnotations = ['http://0.0.0.0/entry4', 'http://0.0.0.0/entry5', 'http://0.0.0.0/entry6', 'http://0.0.0.0/entry7'];
|
||||
|
||||
foreach ($content['_embedded']['items'] as $item) {
|
||||
$this->assertNotContains($item['url'], $entriesWithAnnotations, 'Entry with annotations should NOT be in the results');
|
||||
if (\in_array($item['url'], $entriesWithoutAnnotations, true)) {
|
||||
$this->assertEmpty($item['annotations'], 'Entry with URL ' . $item['url'] . ' should not have annotations');
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure we have at least some entries without annotations
|
||||
$foundUrls = array_column($content['_embedded']['items'], 'url');
|
||||
$foundWithoutAnnotations = array_intersect($foundUrls, $entriesWithoutAnnotations);
|
||||
$this->assertNotEmpty($foundWithoutAnnotations, 'Should have at least one entry without annotations in the results');
|
||||
|
||||
// Check pagination links contain the filter
|
||||
$this->assertArrayHasKey('_links', $content);
|
||||
foreach (['self', 'first', 'last'] as $link) {
|
||||
$this->assertArrayHasKey('href', $content['_links'][$link]);
|
||||
$this->assertStringContainsString('annotations=0', $content['_links'][$link]['href']);
|
||||
}
|
||||
|
||||
$this->assertSame('application/json', $this->client->getResponse()->headers->get('Content-Type'));
|
||||
}
|
||||
|
||||
public function testGetEntriesOnPageTwo()
|
||||
{
|
||||
$this->client->request('GET', '/api/entries', [
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
namespace Tests\Wallabag\Controller;
|
||||
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Scheb\TwoFactorBundle\Security\TwoFactor\Provider\Google\GoogleAuthenticatorInterface;
|
||||
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
||||
use Symfony\Component\HttpFoundation\Session\SessionInterface;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
|
||||
@ -1223,27 +1224,74 @@ class ConfigControllerTest extends WallabagTestCase
|
||||
|
||||
public function testUserEnable2faGoogle()
|
||||
{
|
||||
$googleAuthenticatorMock = $this->createMock(GoogleAuthenticatorInterface::class);
|
||||
$googleAuthenticatorMock
|
||||
->method('generateSecret')
|
||||
->willReturn('DUMMYSECRET');
|
||||
$googleAuthenticatorMock
|
||||
->method('checkCode')
|
||||
->willReturnCallback(function ($user, $code) {
|
||||
return '123456' === $code;
|
||||
});
|
||||
|
||||
$this->logInAs('admin');
|
||||
$client = $this->getTestClient();
|
||||
$client->disableReboot(); // Disable reboot to keep the mock in the container
|
||||
|
||||
// name::class notation does not work in this context
|
||||
self::getContainer()->set('scheb_two_factor.security.google_authenticator', $googleAuthenticatorMock);
|
||||
|
||||
$crawler = $client->request('GET', '/config');
|
||||
|
||||
$form = $crawler->filter('form[name=config_otp_app]')->form();
|
||||
$client->submit($form);
|
||||
$crawler = $client->submit($form);
|
||||
|
||||
$this->assertSame(200, $client->getResponse()->getStatusCode());
|
||||
|
||||
// restore user
|
||||
$secret = $crawler->filter('div#config_otp_app_secret pre code')->innerText();
|
||||
$this->assertSame('DUMMYSECRET', $secret);
|
||||
|
||||
$em = $this->getEntityManager();
|
||||
$user = $em
|
||||
->getRepository(User::class)
|
||||
->findOneByUsername('admin');
|
||||
// At this phase, the user should not have 2FA enabled
|
||||
$this->assertFalse($user->isGoogleTwoFactor());
|
||||
|
||||
// First test: send invalid OTP code
|
||||
$form = $crawler->filter('form[name=config_otp_app_check]')->form();
|
||||
$data = [
|
||||
'_auth_code' => '000000',
|
||||
];
|
||||
$client->submit($form, $data);
|
||||
|
||||
$this->assertSame(307, $client->getResponse()->getStatusCode());
|
||||
$this->assertStringContainsString('flashes.config.notice.otp_code_invalid', $client->getContainer()->get(SessionInterface::class)->getFlashBag()->get('notice')[0]);
|
||||
|
||||
// Follow the redirect to the OTP check form again
|
||||
$crawler = $client->followRedirect();
|
||||
|
||||
// Second test: send valid OTP code
|
||||
$form = $crawler->filter('form[name=config_otp_app_check]')->form();
|
||||
$data = [
|
||||
'_auth_code' => '123456',
|
||||
];
|
||||
$client->submit($form, $data);
|
||||
|
||||
$this->assertSame(302, $client->getResponse()->getStatusCode());
|
||||
$this->assertStringContainsString('flashes.config.notice.otp_enabled', $client->getContainer()->get(SessionInterface::class)->getFlashBag()->get('notice')[0]);
|
||||
|
||||
$em = $this->getEntityManager();
|
||||
$user = $em
|
||||
->getRepository(User::class)
|
||||
->findOneByUsername('admin');
|
||||
|
||||
$this->assertTrue($user->isGoogleTwoFactor());
|
||||
$this->assertGreaterThan(0, $user->getBackupCodes());
|
||||
$this->assertGreaterThan(0, \count($user->getBackupCodes()));
|
||||
|
||||
$user->setGoogleAuthenticatorSecret(false);
|
||||
$user->setBackupCodes(null);
|
||||
// Restore user
|
||||
$user->setGoogleAuthenticatorSecret('');
|
||||
$user->setGoogleAuthenticator(false);
|
||||
$user->setBackupCodes([]);
|
||||
$em->persist($user);
|
||||
$em->flush();
|
||||
}
|
||||
@ -1259,6 +1307,7 @@ class ConfigControllerTest extends WallabagTestCase
|
||||
->findOneByUsername('admin');
|
||||
|
||||
$user->setGoogleAuthenticatorSecret('Google2FA');
|
||||
$user->setGoogleAuthenticator(true);
|
||||
$em->persist($user);
|
||||
$em->flush();
|
||||
|
||||
@ -1271,7 +1320,6 @@ class ConfigControllerTest extends WallabagTestCase
|
||||
|
||||
$this->assertStringContainsString('flashes.config.notice.otp_disabled', $client->getContainer()->get(SessionInterface::class)->getFlashBag()->get('notice')[0]);
|
||||
|
||||
// restore user
|
||||
$em = $this->getEntityManager();
|
||||
$user = $em
|
||||
->getRepository(User::class)
|
||||
@ -1279,6 +1327,7 @@ class ConfigControllerTest extends WallabagTestCase
|
||||
|
||||
$this->assertEmpty($user->getGoogleAuthenticatorSecret());
|
||||
$this->assertEmpty($user->getBackupCodes());
|
||||
$this->assertFalse($user->isGoogleTwoFactor());
|
||||
}
|
||||
|
||||
public function testExportTaggingRule()
|
||||
|
||||
@ -66,6 +66,7 @@ class SecurityControllerTest extends WallabagTestCase
|
||||
->getRepository(User::class)
|
||||
->findOneByUsername('admin');
|
||||
$user->setGoogleAuthenticatorSecret('26LDIHYGHNELOQEM');
|
||||
$user->setGoogleAuthenticator(true);
|
||||
$em->persist($user);
|
||||
$em->flush();
|
||||
|
||||
@ -78,6 +79,7 @@ class SecurityControllerTest extends WallabagTestCase
|
||||
->getRepository(User::class)
|
||||
->findOneByUsername('admin');
|
||||
$user->setGoogleAuthenticatorSecret(null);
|
||||
$user->setGoogleAuthenticator(false);
|
||||
$em->persist($user);
|
||||
$em->flush();
|
||||
}
|
||||
|
||||
@ -566,6 +566,7 @@ import:
|
||||
pocket_csv:
|
||||
page_title: Import > Pocket CSV
|
||||
description: Tento importér naimportuje všechny vaše záložky z Pocketu (prostřednictvím CSV exportu). Stačí přejít na https://getpocket.com/export a exportovat soubor. Stáhne se ZIP soubor (např. „pocket.zip“). Po rozbalení získáte CSV soubor, nazvaný „part_000000.csv“.
|
||||
how_to: Zvolte soubor zálohy záložek a kliknutím na níže uvedené tlačítko jej naimportujte. Pamatujte na to, že tento proces může trvat dlouho, protože je třeba načíst všechny články.
|
||||
flashes:
|
||||
config:
|
||||
notice:
|
||||
|
||||
@ -237,6 +237,16 @@ entry:
|
||||
untagged: Untagged entries
|
||||
all: All entries
|
||||
same_domain: Same domain
|
||||
sort:
|
||||
status_label: Sort by
|
||||
order_label: Order
|
||||
by:
|
||||
creation_date: Creation date
|
||||
title: Title
|
||||
url: URL
|
||||
reading_time: Reading time
|
||||
ascending: Ascending
|
||||
descending: Descending
|
||||
list:
|
||||
number_on_the_page: '{0} There are no entries.|{1} There is one entry.|]1,Inf[ There are %count% entries.'
|
||||
reading_time: estimated reading time
|
||||
|
||||
Reference in New Issue
Block a user