mirror of
https://github.com/wallabag/wallabag.git
synced 2025-12-26 16:27:34 +01:00
Compare commits
91 Commits
articles-w
...
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 | |||
| 064b9c4415 | |||
| 3ef9e56478 | |||
| 9460df5e37 | |||
| 76fa0d940b | |||
| 0130cca0c1 | |||
| 17d44e4ff6 | |||
| f410c36736 | |||
| 923cf60604 | |||
| 546e20ae5e | |||
| 5b1bda636e | |||
| 9068e51c54 | |||
| b73638b7ec | |||
| fbe01e78a4 | |||
| cb6a4cf91a | |||
| 99f2d527fc | |||
| 4f98befd5f | |||
| f6a8412a82 | |||
| 02a30c6438 | |||
| 4fd7c7cce7 | |||
| 9e1b91b057 | |||
| 54dcb72288 | |||
| 97bedb1b0c | |||
| d5fa287d21 | |||
| a648efdcce | |||
| 85e53eb903 | |||
| d405145686 | |||
| 4d0b509e7d | |||
| 7a4aca6d8c | |||
| e90bbaf540 | |||
| 049d87e180 | |||
| 0d1748c8a8 | |||
| 540483c583 | |||
| 95de911efd | |||
| 9cae4c6410 | |||
| 2dc1a2b94c |
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",
|
||||
|
||||
220
composer.lock
generated
220
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",
|
||||
@ -776,16 +776,16 @@
|
||||
},
|
||||
{
|
||||
"name": "doctrine/dbal",
|
||||
"version": "3.9.4",
|
||||
"version": "3.9.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/doctrine/dbal.git",
|
||||
"reference": "ec16c82f20be1a7224e65ac67144a29199f87959"
|
||||
"reference": "4a4e2eed3134036ee36a147ee0dac037dfa17868"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/doctrine/dbal/zipball/ec16c82f20be1a7224e65ac67144a29199f87959",
|
||||
"reference": "ec16c82f20be1a7224e65ac67144a29199f87959",
|
||||
"url": "https://api.github.com/repos/doctrine/dbal/zipball/4a4e2eed3134036ee36a147ee0dac037dfa17868",
|
||||
"reference": "4a4e2eed3134036ee36a147ee0dac037dfa17868",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -798,14 +798,14 @@
|
||||
"psr/log": "^1|^2|^3"
|
||||
},
|
||||
"require-dev": {
|
||||
"doctrine/coding-standard": "12.0.0",
|
||||
"doctrine/coding-standard": "13.0.0",
|
||||
"fig/log-test": "^1",
|
||||
"jetbrains/phpstorm-stubs": "2023.1",
|
||||
"phpstan/phpstan": "2.1.1",
|
||||
"phpstan/phpstan": "2.1.17",
|
||||
"phpstan/phpstan-strict-rules": "^2",
|
||||
"phpunit/phpunit": "9.6.22",
|
||||
"slevomat/coding-standard": "8.13.1",
|
||||
"squizlabs/php_codesniffer": "3.10.2",
|
||||
"phpunit/phpunit": "9.6.23",
|
||||
"slevomat/coding-standard": "8.16.2",
|
||||
"squizlabs/php_codesniffer": "3.13.1",
|
||||
"symfony/cache": "^5.4|^6.0|^7.0",
|
||||
"symfony/console": "^4.4|^5.4|^6.0|^7.0"
|
||||
},
|
||||
@ -867,7 +867,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/doctrine/dbal/issues",
|
||||
"source": "https://github.com/doctrine/dbal/tree/3.9.4"
|
||||
"source": "https://github.com/doctrine/dbal/tree/3.9.5"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@ -883,7 +883,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-01-16T08:28:55+00:00"
|
||||
"time": "2025-06-15T22:40:05+00:00"
|
||||
},
|
||||
{
|
||||
"name": "doctrine/deprecations",
|
||||
@ -1470,16 +1470,16 @@
|
||||
},
|
||||
{
|
||||
"name": "doctrine/migrations",
|
||||
"version": "3.9.0",
|
||||
"version": "3.9.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/doctrine/migrations.git",
|
||||
"reference": "325b61e41d032f5f7d7e2d11cbefff656eadc9ab"
|
||||
"reference": "0f1e0c960ac29866d648a4f50142a74fe1cb6999"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/doctrine/migrations/zipball/325b61e41d032f5f7d7e2d11cbefff656eadc9ab",
|
||||
"reference": "325b61e41d032f5f7d7e2d11cbefff656eadc9ab",
|
||||
"url": "https://api.github.com/repos/doctrine/migrations/zipball/0f1e0c960ac29866d648a4f50142a74fe1cb6999",
|
||||
"reference": "0f1e0c960ac29866d648a4f50142a74fe1cb6999",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -1553,7 +1553,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/doctrine/migrations/issues",
|
||||
"source": "https://github.com/doctrine/migrations/tree/3.9.0"
|
||||
"source": "https://github.com/doctrine/migrations/tree/3.9.1"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@ -1569,7 +1569,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-03-26T06:48:45+00:00"
|
||||
"time": "2025-06-27T07:19:23+00:00"
|
||||
},
|
||||
{
|
||||
"name": "doctrine/orm",
|
||||
@ -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",
|
||||
@ -12653,7 +12654,7 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/stopwatch",
|
||||
"version": "v7.2.4",
|
||||
"version": "v7.3.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/stopwatch.git",
|
||||
@ -12695,7 +12696,7 @@
|
||||
"description": "Provides a way to profile code",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/stopwatch/tree/v7.2.4"
|
||||
"source": "https://github.com/symfony/stopwatch/tree/v7.3.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@ -13455,20 +13456,21 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/var-exporter",
|
||||
"version": "v7.2.6",
|
||||
"version": "v7.3.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/var-exporter.git",
|
||||
"reference": "422b8de94c738830a1e071f59ad14d67417d7007"
|
||||
"reference": "c9a1168891b5aaadfd6332ef44393330b3498c4c"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/var-exporter/zipball/422b8de94c738830a1e071f59ad14d67417d7007",
|
||||
"reference": "422b8de94c738830a1e071f59ad14d67417d7007",
|
||||
"url": "https://api.github.com/repos/symfony/var-exporter/zipball/c9a1168891b5aaadfd6332ef44393330b3498c4c",
|
||||
"reference": "c9a1168891b5aaadfd6332ef44393330b3498c4c",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.2"
|
||||
"php": ">=8.2",
|
||||
"symfony/deprecation-contracts": "^2.5|^3"
|
||||
},
|
||||
"require-dev": {
|
||||
"symfony/property-access": "^6.4|^7.0",
|
||||
@ -13511,7 +13513,7 @@
|
||||
"serialize"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/var-exporter/tree/v7.2.6"
|
||||
"source": "https://github.com/symfony/var-exporter/tree/v7.3.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@ -13527,7 +13529,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-05-02T08:36:00+00:00"
|
||||
"time": "2025-05-15T09:04:05+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/webpack-encore-bundle",
|
||||
@ -15119,16 +15121,16 @@
|
||||
},
|
||||
{
|
||||
"name": "doctrine/data-fixtures",
|
||||
"version": "2.0.2",
|
||||
"version": "2.0.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/doctrine/data-fixtures.git",
|
||||
"reference": "f7f1e12d6bceb58c204b3e77210a103c1c57601e"
|
||||
"reference": "f65b353922b7ac48f360428e19b22fcce5aba134"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/doctrine/data-fixtures/zipball/f7f1e12d6bceb58c204b3e77210a103c1c57601e",
|
||||
"reference": "f7f1e12d6bceb58c204b3e77210a103c1c57601e",
|
||||
"url": "https://api.github.com/repos/doctrine/data-fixtures/zipball/f65b353922b7ac48f360428e19b22fcce5aba134",
|
||||
"reference": "f65b353922b7ac48f360428e19b22fcce5aba134",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -15142,14 +15144,14 @@
|
||||
"doctrine/phpcr-odm": "<1.3.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"doctrine/coding-standard": "^12",
|
||||
"doctrine/coding-standard": "^13",
|
||||
"doctrine/dbal": "^3.5 || ^4",
|
||||
"doctrine/mongodb-odm": "^1.3.0 || ^2.0.0",
|
||||
"doctrine/orm": "^2.14 || ^3",
|
||||
"ext-sqlite3": "*",
|
||||
"fig/log-test": "^1",
|
||||
"phpstan/phpstan": "^1.10",
|
||||
"phpunit/phpunit": "^10.5.3",
|
||||
"phpstan/phpstan": "2.1.17",
|
||||
"phpunit/phpunit": "10.5.45",
|
||||
"symfony/cache": "^6.4 || ^7",
|
||||
"symfony/var-exporter": "^6.4 || ^7"
|
||||
},
|
||||
@ -15182,7 +15184,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/doctrine/data-fixtures/issues",
|
||||
"source": "https://github.com/doctrine/data-fixtures/tree/2.0.2"
|
||||
"source": "https://github.com/doctrine/data-fixtures/tree/2.0.3"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@ -15198,7 +15200,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-01-21T13:21:31+00:00"
|
||||
"time": "2025-06-27T19:59:58+00:00"
|
||||
},
|
||||
{
|
||||
"name": "doctrine/doctrine-fixtures-bundle",
|
||||
@ -15845,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",
|
||||
@ -15937,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": [
|
||||
{
|
||||
@ -15945,7 +15948,7 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2025-03-31T18:40:42+00:00"
|
||||
"time": "2025-07-15T18:21:57+00:00"
|
||||
},
|
||||
{
|
||||
"name": "friendsoftwig/twigcs",
|
||||
@ -19073,16 +19076,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/phpunit-bridge",
|
||||
"version": "v7.3.0",
|
||||
"version": "v7.3.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/phpunit-bridge.git",
|
||||
"reference": "2eabda563921f21cbce1d1e3247b3c36568905e6"
|
||||
"reference": "71624984d8bcad6acf7a790d4e3ceafe04bc2485"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/2eabda563921f21cbce1d1e3247b3c36568905e6",
|
||||
"reference": "2eabda563921f21cbce1d1e3247b3c36568905e6",
|
||||
"url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/71624984d8bcad6acf7a790d4e3ceafe04bc2485",
|
||||
"reference": "71624984d8bcad6acf7a790d4e3ceafe04bc2485",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -19134,8 +19137,11 @@
|
||||
],
|
||||
"description": "Provides utilities for PHPUnit, especially user deprecation notices management",
|
||||
"homepage": "https://symfony.com",
|
||||
"keywords": [
|
||||
"testing"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/phpunit-bridge/tree/v7.3.0"
|
||||
"source": "https://github.com/symfony/phpunit-bridge/tree/v7.3.1"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@ -19151,7 +19157,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-05-23T07:26:30+00:00"
|
||||
"time": "2025-06-04T10:09:06+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/process",
|
||||
|
||||
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');
|
||||
}
|
||||
}
|
||||
20
package.json
20
package.json
@ -41,26 +41,26 @@
|
||||
"url": "https://github.com/wallabag/wallabag/issues"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.27.4",
|
||||
"@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.42.0",
|
||||
"core-js": "^3.45.0",
|
||||
"css-loader": "^7.1.2",
|
||||
"eslint": "^8.57.1",
|
||||
"eslint-config-airbnb-base": "^15.0.0",
|
||||
"eslint-plugin-import": "^2.31.0",
|
||||
"eslint-webpack-plugin": "^5.0.1",
|
||||
"eslint-plugin-import": "^2.32.0",
|
||||
"eslint-webpack-plugin": "^5.0.2",
|
||||
"file-loader": "^6.2.0",
|
||||
"lato-font": "^3.0.0",
|
||||
"mini-css-extract-plugin": "^2.9.2",
|
||||
"postcss": "^8.5.4",
|
||||
"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.0",
|
||||
"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",
|
||||
|
||||
@ -9,8 +9,9 @@ use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Wallabag\Entity\Entry;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Wallabag\Entity\User;
|
||||
use Wallabag\Event\EntryDeletedEvent;
|
||||
use Wallabag\Repository\EntryRepository;
|
||||
use Wallabag\Repository\UserRepository;
|
||||
|
||||
@ -26,6 +27,7 @@ class CleanDuplicatesCommand extends Command
|
||||
private readonly EntityManagerInterface $entityManager,
|
||||
private readonly EntryRepository $entryRepository,
|
||||
private readonly UserRepository $userRepository,
|
||||
private readonly EventDispatcherInterface $eventDispatcher,
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
@ -86,7 +88,12 @@ class CleanDuplicatesCommand extends Command
|
||||
if (\in_array($url, $urls, true)) {
|
||||
++$duplicatesCount;
|
||||
|
||||
$this->entityManager->remove($this->entryRepository->find($entry['id']));
|
||||
$entryToDelete = $this->entryRepository->find($entry['id']);
|
||||
|
||||
// entry deleted, dispatch event about it!
|
||||
$this->eventDispatcher->dispatch(new EntryDeletedEvent($entryToDelete), EntryDeletedEvent::NAME);
|
||||
|
||||
$this->entityManager->remove($entryToDelete);
|
||||
$this->entityManager->flush(); // Flushing at the end of the loop would require the instance not being online
|
||||
} else {
|
||||
$urls[] = $entry['url'];
|
||||
|
||||
118
src/Command/Import/UrlCommand.php
Normal file
118
src/Command/Import/UrlCommand.php
Normal file
@ -0,0 +1,118 @@
|
||||
<?php
|
||||
|
||||
namespace Wallabag\Command\Import;
|
||||
|
||||
use Doctrine\DBAL\Driver\Middleware;
|
||||
use Doctrine\DBAL\Logging\Middleware as LoggingMiddleware;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Component\Config\Definition\Exception\Exception;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
|
||||
use Wallabag\Entity\Entry;
|
||||
use Wallabag\Entity\User;
|
||||
use Wallabag\Helper\ContentProxy;
|
||||
use Wallabag\Helper\TagsAssigner;
|
||||
use Wallabag\Repository\UserRepository;
|
||||
|
||||
class UrlCommand extends Command
|
||||
{
|
||||
public function __construct(
|
||||
private readonly EntityManagerInterface $entityManager,
|
||||
private readonly TokenStorageInterface $tokenStorage,
|
||||
private readonly UserRepository $userRepository,
|
||||
private readonly ContentProxy $contentProxy,
|
||||
private readonly TagsAssigner $tagsAssigner,
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName('wallabag:import:url')
|
||||
->setDescription('Import a single URL')
|
||||
->addArgument('username', InputArgument::REQUIRED, 'User to add the URL to (value: username or id)')
|
||||
->addArgument('url', InputArgument::REQUIRED, 'URL to import')
|
||||
->addArgument('tags', InputArgument::OPTIONAL, 'Comma-separated list of tags to add')
|
||||
->addOption('markAsRead', null, InputOption::VALUE_OPTIONAL, 'Mark entry as read', false)
|
||||
->addOption('useUserId', null, InputOption::VALUE_NONE, 'Use user id instead of username to find account')
|
||||
;
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
// Turning off doctrine default logs queries for saving memory
|
||||
$middlewares = $this->entityManager->getConnection()->getConfiguration()->getMiddlewares();
|
||||
$middlewaresWithoutLogging = array_filter($middlewares, fn (Middleware $middleware) => !$middleware instanceof LoggingMiddleware);
|
||||
$this->entityManager->getConnection()->getConfiguration()->setMiddlewares($middlewaresWithoutLogging);
|
||||
|
||||
if ($input->getOption('useUserId')) {
|
||||
$entityUser = $this->userRepository->findOneById($input->getArgument('username'));
|
||||
} else {
|
||||
$entityUser = $this->userRepository->findOneByUsername($input->getArgument('username'));
|
||||
}
|
||||
|
||||
if (!\is_object($entityUser)) {
|
||||
throw new Exception(\sprintf('User "%s" not found', $input->getArgument('username')));
|
||||
}
|
||||
|
||||
// Authenticate user for paywalled websites
|
||||
$token = new UsernamePasswordToken(
|
||||
$entityUser,
|
||||
'main',
|
||||
$entityUser->getRoles()
|
||||
);
|
||||
|
||||
$this->tokenStorage->setToken($token);
|
||||
$user = $this->tokenStorage->getToken()->getUser();
|
||||
\assert($user instanceof User);
|
||||
|
||||
$url = $input->getArgument('url');
|
||||
|
||||
$existingEntry = $this->entityManager
|
||||
->getRepository(Entry::class)
|
||||
->findByUrlAndUserId($url, $user->getId());
|
||||
|
||||
if (false !== $existingEntry) {
|
||||
$output->writeln(\sprintf('The URL %s is already in user’s entries.', $url));
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
$entry = new Entry($user);
|
||||
|
||||
try {
|
||||
$this->contentProxy->updateEntry($entry, $url);
|
||||
} catch (\Exception $e) {
|
||||
$output->writeln(\sprintf('Error trying to import the URL %s: %s.', $url, $e->getMessage()));
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
if ($input->getOption('markAsRead')) {
|
||||
$entry->updateArchived(true);
|
||||
}
|
||||
|
||||
$this->entityManager->persist($entry);
|
||||
|
||||
$tags = explode(',', $input->getArgument('tags'));
|
||||
if (\count($tags) > 1) {
|
||||
$this->tagsAssigner->assignTagsToEntry(
|
||||
$entry,
|
||||
$tags,
|
||||
$this->entityManager->getUnitOfWork()->getScheduledEntityInsertions()
|
||||
);
|
||||
}
|
||||
|
||||
$this->entityManager->flush();
|
||||
|
||||
$output->writeln(\sprintf('URL %s successfully imported.', $url));
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@ -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">
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
99
tests/Command/Import/UrlCommandTest.php
Normal file
99
tests/Command/Import/UrlCommandTest.php
Normal file
@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Wallabag\Command\Import;
|
||||
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\NoResultException;
|
||||
use Symfony\Bundle\FrameworkBundle\Console\Application;
|
||||
use Symfony\Component\Console\Exception\RuntimeException;
|
||||
use Symfony\Component\Console\Tester\CommandTester;
|
||||
use Tests\Wallabag\WallabagTestCase;
|
||||
use Wallabag\Entity\Entry;
|
||||
|
||||
class UrlCommandTest extends WallabagTestCase
|
||||
{
|
||||
private $url = 'https://www.20minutes.fr/sport/football/4158082-20250612-euro-espoirs-su-souffrir-ensemble-decimes-bleuets-retiennent-positif-apres-nul-face-portugal';
|
||||
|
||||
public function testRunUrlCommandWithoutArguments()
|
||||
{
|
||||
$this->expectException(RuntimeException::class);
|
||||
$this->expectExceptionMessage('Not enough arguments');
|
||||
|
||||
$application = new Application($this->getTestClient()->getKernel());
|
||||
|
||||
$command = $application->find('wallabag:import:url');
|
||||
|
||||
$tester = new CommandTester($command);
|
||||
$tester->execute([]);
|
||||
}
|
||||
|
||||
public function testRunUrlCommandWithWrongUsername()
|
||||
{
|
||||
$this->expectException(NoResultException::class);
|
||||
|
||||
$application = new Application($this->getTestClient()->getKernel());
|
||||
|
||||
$command = $application->find('wallabag:import:url');
|
||||
|
||||
$tester = new CommandTester($command);
|
||||
$tester->execute([
|
||||
'username' => 'random',
|
||||
'url' => $this->url,
|
||||
]);
|
||||
}
|
||||
|
||||
public function testRunUrlCommand()
|
||||
{
|
||||
$application = new Application($this->getTestClient()->getKernel());
|
||||
|
||||
$command = $application->find('wallabag:import:url');
|
||||
|
||||
$tester = new CommandTester($command);
|
||||
$tester->execute([
|
||||
'username' => 'admin',
|
||||
'url' => $this->url,
|
||||
]);
|
||||
|
||||
$this->assertStringContainsString('successfully imported', $tester->getDisplay());
|
||||
}
|
||||
|
||||
public function testRunUrlCommandWithTags()
|
||||
{
|
||||
$application = new Application($this->getTestClient()->getKernel());
|
||||
|
||||
$command = $application->find('wallabag:import:url');
|
||||
|
||||
$tester = new CommandTester($command);
|
||||
$tester->execute([
|
||||
'username' => 'admin',
|
||||
'url' => $this->url,
|
||||
'tags' => 'sport, football',
|
||||
]);
|
||||
|
||||
$this->assertStringContainsString('successfully imported', $tester->getDisplay());
|
||||
|
||||
$client = $this->getTestClient();
|
||||
$em = $client->getContainer()->get(EntityManagerInterface::class);
|
||||
$entry = $em->getRepository(Entry::class)->findByUrlAndUserId($this->url, $this->getLoggedInUserId());
|
||||
$this->assertContains('football', $entry->getTagsLabel());
|
||||
$this->assertNotContains('basketball', $entry->getTagsLabel());
|
||||
}
|
||||
|
||||
public function testRunUrlCommandWithUserId()
|
||||
{
|
||||
$this->logInAs('admin');
|
||||
|
||||
$application = new Application($this->getTestClient()->getKernel());
|
||||
|
||||
$command = $application->find('wallabag:import:url');
|
||||
|
||||
$tester = new CommandTester($command);
|
||||
$tester->execute([
|
||||
'username' => $this->getLoggedInUserId(),
|
||||
'url' => $this->url,
|
||||
'--useUserId' => true,
|
||||
]);
|
||||
|
||||
$this->assertStringContainsString('successfully imported', $tester->getDisplay());
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -558,11 +558,15 @@ import:
|
||||
pocket_html:
|
||||
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.
|
||||
page_title: Import > Pocket HTML
|
||||
description: Tento importér naimportuje všechny vaše záložky z Pocket. Stačí přejít na https://getpocket.com/export) a exportovat soubor HTML, který bude stažen (například „ril_export.html“).
|
||||
description: Tento importér naimportuje všechny vaše záložky z Pocketu (prostřednictvím HTML exportu). Stačí přejít na https://getpocket.com/export a exportovat soubor HTML, který bude stažen (například „ril_export.html“).
|
||||
omnivore:
|
||||
how_to: Rozbalte prosím svůj export z Omnivoru, následně nahrajte jeden po druhém všechny JSON soubory s názevem "metadata_x_to_y.json".
|
||||
page_title: Import > Omnivore
|
||||
description: Tento importér naimportuje všechny vaše články z Omnivore.
|
||||
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:
|
||||
@ -585,6 +589,7 @@ flashes:
|
||||
user_updated: Informace byla aktualizována
|
||||
config_saved: Konfigurace byla uložena.
|
||||
tagging_rules_reset: Pravidla štítkování byla obnovena
|
||||
otp_code_invalid: Neplatný dvoufaktorový autentizační kód
|
||||
ignore_origin_instance_rule:
|
||||
notice:
|
||||
deleted: Globální pravidlo ignorování bylo odstraněno
|
||||
|
||||
@ -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
|
||||
|
||||
@ -115,6 +115,26 @@ import:
|
||||
action:
|
||||
import_contents: 콘텐츠 가져오기
|
||||
page_title: 가져오기
|
||||
delicious:
|
||||
how_to: Delicious 내보내기를 선택하고 아래 버튼을 클릭하여 업로드하고 가져오십시오.
|
||||
page_title: 가져오기 > del.icio.us
|
||||
description: '이 가져오기 도구는 모든 Delicious 북마크를 가져옵니다. 2021년부터 내보내기 페이지 (https://del.icio.us/export)를 사용하여 데이터를 다시 내보낼 수 있습니다. "JSON" 형식을 선택하고 다운로드하십시오 (예: "delicious_export.2021.02.06_21.10.json").'
|
||||
shaarli:
|
||||
page_title: 가져오기 > Shaarli
|
||||
description: 이 가져오기 도구는 모든 Shaarli 북마크를 가져옵니다. 도구 섹션으로 이동한 다음 "데이터베이스 내보내기"에서 북마크를 선택하고 내보내십시오. HTML 파일을 얻게 됩니다.
|
||||
how_to: 북마크 백업 파일을 선택하고 아래 버튼을 클릭하여 가져오십시오. 모든 문서를 가져와야하므로 처리 시간이 오래 걸릴 수 있습니다.
|
||||
omnivore:
|
||||
page_title: 가져오기 > Omnivore
|
||||
description: 이 가져오기 도구는 모든 Omnivore 문서를 가져옵니다.
|
||||
how_to: Omnivore 내보내기 압축을 풀고 "metadata_x_to_y.json" 이라는 각 JSON 파일을 하나씩 업로드하십시오.
|
||||
pocket_html:
|
||||
description: '이 가져오기 도구는 모든 Pocket 북마크 (HTML 내보내기 통해)를 가져옵니다. https://getpocket.com/export 로 이동한 다음 HTML 파일을 내보내십시오. HTML 파일이 다운로드됩니다 (예: "ril_export.html").'
|
||||
page_title: 가져오기 > Pocket HTML
|
||||
how_to: 북마크 백업 파일을 선택하고 아래 버튼을 클릭하여 가져오십시오. 모든 문서를 가져와야하므로 처리 시간이 오래 걸릴 수 있습니다.
|
||||
pocket_csv:
|
||||
description: '이 가져오기 도구는 모든 Pocket 북마크 (CSV 내보내기 통해)를 가져옵니다. https://getpocket.com/export 로 이동한 다음 파일을 내보내십시오. ZIP 파일이 다운로드됩니다 (예: "pocket.zip"). 압축을 풀면 "part_000000.csv" 라는 CSV 파일을 얻게 됩니다.'
|
||||
how_to: 북마크 백업 파일을 선택하고 아래 버튼을 클릭하여 가져오십시오. 모든 문서를 가져와야하므로 처리 시간이 오래 걸릴 수 있습니다.
|
||||
page_title: 가져오기 > Pocket CSV
|
||||
about:
|
||||
third_party:
|
||||
description: '다음은 wallabag에 사용되는 타사 라이브러리 목록입니다 (저작권 포함):'
|
||||
@ -199,6 +219,8 @@ entry:
|
||||
starred_label: 즐겨찾기
|
||||
status_label: 상태
|
||||
title: 필터
|
||||
annotated_label: 주석 있음
|
||||
parsed_label: 올바르게 가져오지 못함
|
||||
edit:
|
||||
origin_url_label: 원본 URL (원본 문서의 위치)
|
||||
page_title: 문서 수정
|
||||
@ -219,9 +241,13 @@ entry:
|
||||
filtered_tags: '태그로 필터링:'
|
||||
filtered: 필터링한 기사
|
||||
unread: 읽지않은 기사
|
||||
with_annotations: 주석이 있는 문서
|
||||
same_domain: 동일 도메인
|
||||
confirm:
|
||||
delete_tag: 문서에서 해당 태그를 제거 하시겠습니까?
|
||||
delete: 해당 문서를 제거 하시겠습니까?
|
||||
delete_entries: 이 문서를 제거 하시겠습니까?
|
||||
reload: 해당 문서를 다시 로드 하시겠습니까?
|
||||
public:
|
||||
shared_by_wallabag: 이 문서는 %username% 님이 <a href='%wallabag_instance%'>wallabag</a>와 공유했습니다
|
||||
metadata:
|
||||
@ -238,13 +264,18 @@ entry:
|
||||
toogle_as_read: 읽음으로 전환
|
||||
toogle_as_star: 즐겨찾기 전환
|
||||
original_article: 원본
|
||||
reading_time_less_one_minute_short: '< 1 분'
|
||||
reading_time_less_one_minute_short: '< 1 분'
|
||||
reading_time_minutes_short: '%readingTime% 분'
|
||||
number_of_tags: '{1} 및 1 개의 다른 태그|]1,Inf[및 %count% 다른 태그'
|
||||
reading_time_less_one_minute: '예상 읽기 시간: < 1 분'
|
||||
reading_time_less_one_minute: '예상 읽기 시간: < 1 분'
|
||||
reading_time_minutes: '예상 읽기 시간: %readingTime% 분'
|
||||
reading_time: 예상 읽기 시간
|
||||
number_on_the_page: '{0} 기사가 없습니다.|{1} 기사가 하나 있습니다.|]1,Inf[ %count % 기사가 있습니다.'
|
||||
number_on_the_page: '{0} 기사가 없습니다.|{1} 기사가 하나 있습니다.|]1,Inf[ %count%개 기사가 있습니다.'
|
||||
assign_search_tag: 이 검색을 각 결과에 태그로 할당
|
||||
show_same_domain: 동일 도메인을 가진 문서 보기
|
||||
toggle_mass_action: 대량 작업 전환
|
||||
mass_action_tags_input_placeholder: 태그1, 태그2, 태그3
|
||||
add_tags: 태그 추가
|
||||
default_title: 기사의 제목
|
||||
howto:
|
||||
bookmarklet:
|
||||
@ -283,7 +314,7 @@ howto:
|
||||
via_google_play: 구글 플레이를 통해
|
||||
via_f_droid: F-Droid를 통해
|
||||
windows: 마이크로소프트 스토어에서
|
||||
ios: 아이튠즈 스토어에서
|
||||
ios: 앱스토어에서
|
||||
browser_addons:
|
||||
opera: 오페라 애드온
|
||||
chrome: 크롬 애드온
|
||||
@ -331,6 +362,7 @@ flashes:
|
||||
tagging_rules_deleted: 태그 지정 규칙이 삭제되었습니다
|
||||
tagging_rules_updated: 태그 지정 규칙이 업데이트되었습니다
|
||||
password_updated: 비밀번호가 업데이트 되었습니다
|
||||
otp_code_invalid: 2단계 인증 코드가 유효하지 않습니다
|
||||
ignore_origin_instance_rule:
|
||||
notice:
|
||||
deleted: 전역 원본 무시 규칙이 삭제되었습니다
|
||||
@ -363,6 +395,7 @@ flashes:
|
||||
notice:
|
||||
tag_renamed: 태그 이름이 변경되었습니다
|
||||
tag_added: 태그가 추가되었습니다
|
||||
too_much_tags: 성능 문제 방지를 위해 한 번에 %tags%개 이상의 태그나 %characters%자 이상의 태그를 추가할 수 없습니다.
|
||||
config:
|
||||
form_ignore_origin_rules:
|
||||
faq:
|
||||
@ -396,7 +429,7 @@ config:
|
||||
title: 문서 제목
|
||||
label: 변수
|
||||
tagging_rules_definition_description: wallabag 에서 자동으로 새 문서에 태그를 지정하는 데 사용하는 규칙입니다. <br /> 태그 지정 규칙이 새 문서가 추가 될 때마다 태그를 구성하는 데 사용되므로, 수동으로 분류하는 수고를 덜 수 있습니다.
|
||||
how_to_use_them_description: '읽기 시간이 3 분 미만인 경우 «<i> 짧은 읽기 </i>» 와 같은 새 기사에 태그를 지정한다고 가정합니다. <br />이 경우 <i> 규칙</i> 을 입력해야합니다. <i>태그</i> 필드에 « readingTime <= 3 » 을 입력하고 « <i>짧은 읽기</i>» 를 입력하십시오. <br /> 여러 태그를 쉼표로구분하여 한 번에 추가 할 수 있습니다: « <i> 짧은 읽기, 반드시 읽기</i> » <br /> 사전 정의 된 연산자를 사용하여 복잡한 규칙을 작성할 수 있습니다: 만약 « <i>readingTime >= 5 AND domainName = "www.php.net"</i> » 다음으로 태그 «<i> 긴 읽기, php</i>»'
|
||||
how_to_use_them_description: '읽기 시간이 3 분 미만인 경우 «<i> 짧은 읽기 </i>» 와 같은 새 기사에 태그를 지정한다고 가정합니다. <br />이 경우 <i> 규칙</i> 을 입력해야합니다. <i>태그</i> 필드에 « readingTime <= 3 » 을 입력하고 « <i>짧은 읽기</i>» 를 입력하십시오. <br /> 여러 태그를 쉼표로구분하여 한 번에 추가 할 수 있습니다: « <i> 짧은 읽기, 반드시 읽기</i> » <br /> 사전 정의 된 연산자를 사용하여 복잡한 규칙을 작성할 수 있습니다: 만약 « <i>readingTime >= 5 AND domainName = "www.php.net"</i> » 다음으로 태그 «<i> 긴 읽기, php</i>»'
|
||||
operator_description:
|
||||
and: 하나의 규칙 그리고 다른 규칙
|
||||
or: 하나의 규칙 또는 다른 규칙
|
||||
@ -438,6 +471,7 @@ config:
|
||||
annotations: 모든 주석 제거
|
||||
confirm: 정말 하시겠습니까? (되돌릴 수 없습니다)
|
||||
tags: 모든 태그 제거
|
||||
tagging_rules: 모든 태그 지정 규칙 제거
|
||||
form_feed:
|
||||
feed_link:
|
||||
archive: 보관
|
||||
@ -493,6 +527,16 @@ config:
|
||||
help_message: '온라인 도구를 사용하여 읽기 속도를 측정 할 수 있습니다:'
|
||||
label: 읽기 속도
|
||||
language_label: 언어
|
||||
help_font: 사용하고 싶은 글꼴 모음을 선택할 수 있습니다.
|
||||
help_fontsize: 사용하고 싶은 글꼴 크기를 선택할 수 있습니다.
|
||||
help_lineheight: 사용하고 싶은 줄 높이를 선택할 수 있습니다.
|
||||
lineheight_label: 줄 높이
|
||||
maxwidth_label: 최대 너비
|
||||
help_maxwidth: 사용하고 싶은 최대 너비를 선택할 수 있습니다.
|
||||
font_label: 글꼴 모음
|
||||
fontsize_label: 글꼴 크기
|
||||
help_display_thumbnails: 문서 썸네일을 표시할지 여부를 결정할 수 있습니다. 느린 연결에 유용합니다.
|
||||
display_thumbnails_label: 문서 썸네일 표시 (느린 연결에 유용).
|
||||
otp:
|
||||
app:
|
||||
qrcode_label: QR 코드
|
||||
@ -513,6 +557,7 @@ config:
|
||||
user_info: 사용자 정보
|
||||
feed: 피드
|
||||
settings: 설정
|
||||
article_display: 문서 표시
|
||||
form:
|
||||
save: 저장
|
||||
page_title: 설정
|
||||
@ -526,7 +571,7 @@ menu:
|
||||
random_entry: 해당 목록에서 임의의 문서로 이동
|
||||
add_new_entry: 새 문서 추가
|
||||
filter_entries: 기사 필터
|
||||
account: 나의 계정
|
||||
account: 내 계정
|
||||
export: 내보내기
|
||||
search: 검색
|
||||
left:
|
||||
@ -541,7 +586,7 @@ menu:
|
||||
theme_toggle_auto: 자동 테마
|
||||
theme_toggle_dark: 어두운 테마
|
||||
theme_toggle_light: 밝은 테마
|
||||
quickstart: 빠른시작
|
||||
quickstart: 빠른 시작
|
||||
search: 검색
|
||||
users_management: 사용자 관리
|
||||
save_link: 링크 저장
|
||||
@ -552,6 +597,7 @@ menu:
|
||||
tags: 태그
|
||||
config: 설정
|
||||
starred: 즐겨찾기
|
||||
with_annotations: 주석 있음
|
||||
search_form:
|
||||
input_label: 여기에 검색어 입력
|
||||
error:
|
||||
@ -636,7 +682,7 @@ quickstart:
|
||||
configure:
|
||||
tagging_rules: 문서를 자동으로 태그하는 규칙 작성
|
||||
feed: 피드 활성화
|
||||
language: 언어 및 디자인 변경
|
||||
language: 언어 및 인터페이스 변경
|
||||
description: 자신에게 맞는 앱을 얻으려면, wallabag의 구성을 살펴보십시오.
|
||||
title: 응용 프로그램 구성
|
||||
more: 더보기…
|
||||
@ -693,3 +739,5 @@ tag:
|
||||
see_untagged_entries: 태그가 없는 문서보기
|
||||
number_on_the_page: '{0} 태그가 없습니다.|{1} 태그가 1 개 있습니다.|]1,Inf[태그가 %count% 개 있습니다.'
|
||||
page_title: 태그
|
||||
confirm:
|
||||
delete: '%name% 태그 삭제'
|
||||
|
||||
@ -20,7 +20,7 @@ config:
|
||||
tagging_rules_definition_title: “குறிச்சொல் விதிகள்” என்றால் என்ன?
|
||||
tagging_rules_definition_description: அவை புதிய உள்ளீடுகளை தானாகக் குறிக்க வாலபாக் பயன்படுத்தும் விதிகள். <br /> ஒவ்வொரு முறையும் ஒரு புதிய நுழைவு சேர்க்கப்படும்போது, நீங்கள் கட்டமைத்த குறிச்சொற்களைச் சேர்க்க அனைத்து குறிச்சொல் விதிகளும் பயன்படுத்தப்படும், இதனால் உங்கள் உள்ளீடுகளை கைமுறையாக வகைப்படுத்துவதில் சிக்கலைச் சேமிக்கிறது.
|
||||
how_to_use_them_title: அவற்றை எவ்வாறு பயன்படுத்துவது?
|
||||
how_to_use_them_description: 'புதிய உள்ளீடுகளை «<i> குறுகிய வாசிப்பு < /i>» எனக் குறிக்க விரும்புகிறீர்கள் என்று வைத்துக் கொள்வோம். வாசிப்பு நேரம் 3 நிமிடங்களுக்குள் இருக்கும்போது. <br /> அந்த விசயத்தில், நீங்கள் «வாசிப்பு நேரத்தை & lt; = 3» <i> விதி </i> புலம் மற்றும் «<i> குறுகிய வாசிப்பு </i>» <i> குறிச்சொற்கள் </i> புலத்தில். <br /> பல குறிச்சொற்கள் ஒரே நேரத்தில் கமாவுடன் பிரிப்பதன் மூலம் சேர்க்கலாம்: « <i> குறுகிய வாசிப்பு, கட்டாயம் படிக்க வேண்டும் </i> »<br /> முன் வரையறுக்கப்பட்ட ஆபரேட்டர்களைப் பயன்படுத்துவதன் மூலம் சிக்கலான விதிகளை எழுதலாம்: என்றால்« <i> வாசிப்பு நேரம் & gt; = 5 மற்றும் டொமைன் பெயர் = "www.php.net" </i > »பின்னர்« <i> நீண்ட வாசிப்பு, பிஎச்பி </i> »என குறிக்கவும்'
|
||||
how_to_use_them_description: 'புதிய உள்ளீடுகளை «<i> குறுகிய வாசிப்பு </i>» எனக் குறிக்க விரும்புகிறீர்கள் என்று வைத்துக் கொள்வோம். வாசிப்பு நேரம் 3 நிமிடங்களுக்குள் இருக்கும்போது. <br /> அந்த விசயத்தில், நீங்கள் «வாசிப்பு நேரத்தை < = 3» <i> விதி </i> புலம் மற்றும் «<i> குறுகிய வாசிப்பு </i>» <i> குறிச்சொற்கள் </i> புலத்தில். <br /> பல குறிச்சொற்கள் ஒரே நேரத்தில் கமாவுடன் பிரிப்பதன் மூலம் சேர்க்கலாம்: «<i> குறுகிய வாசிப்பு, கட்டாயம் படிக்க வேண்டும் </i> »<br /> முன் வரையறுக்கப்பட்ட ஆபரேட்டர்களைப் பயன்படுத்துவதன் மூலம் சிக்கலான விதிகளை எழுதலாம்: என்றால்« <i> வாசிப்பு நேரம் > = 5 மற்றும் டொமைன் பெயர் = "www.php.net" </i> »பின்னர்« <i> நீண்ட வாசிப்பு, பிஎச்பி </i> »எனக் குறிக்கவும்'
|
||||
variables_available_title: விதிகளை எழுத எந்த மாறிகள் மற்றும் ஆபரேட்டர்கள் நான் பயன்படுத்தலாம்?
|
||||
variables_available_description: 'குறிச்சொல் விதிகளை உருவாக்க பின்வரும் மாறிகள் மற்றும் ஆபரேட்டர்கள் பயன்படுத்தப்படலாம்:'
|
||||
operator_description:
|
||||
@ -33,8 +33,8 @@ config:
|
||||
not_equal_to: சமமாக இல்லை…
|
||||
or: ஒரு விதி அல்லது மற்றொரு
|
||||
and: ஒரு விதி மற்றும் மற்றொரு விதி
|
||||
matches: ஒரு <i> பொருள் </i> பொருந்தக்கூடிய சோதனைகள் <i> தேடல் </i> (வழக்கு-உணர்திறன்).
|
||||
notmatches: ஒரு <i> பொருள் </i> பொருந்தாத சோதனைகள் ஒரு <i> தேடல் </is> (வழக்கு-பாதுகாப்பற்ற).
|
||||
matches: 'ஒரு <i> பொருள் </i> பொருந்தக்கூடிய சோதனைகள் <i> தேடல் </i> (வழக்கு-உணர்திறன்).<br />எடுத்துக்காட்டு: <code>தலைப்பு போட்டிகள் "கால்பந்து"</code>'
|
||||
notmatches: 'ஒரு <i> பொருள் </i> பொருந்தாத சோதனைகள் ஒரு <i> தேடல் </i> (வழக்கு-பாதுகாப்பற்ற).<br />எடுத்துக்காட்டு: <code>தலைப்பு பெருந்தாதவைகள் "கால்பந்து"</code>'
|
||||
then_tag_as_label: பின்னர் குறிக்கவும்
|
||||
delete_rule_label: அழி
|
||||
edit_rule_label: தொகு
|
||||
@ -143,9 +143,9 @@ config:
|
||||
faq:
|
||||
title: கேள்விகள்
|
||||
ignore_origin_rules_definition_title: '"தோற்றம் விதிகளை புறக்கணிக்கவும்" என்றால் என்ன?'
|
||||
ignore_origin_rules_definition_description: திருப்பிவிடப்பட்ட பிறகு ஒரு மூல முகவரியை தானாக புறக்கணிக்க அவை வாலபாக் பயன்படுத்துகின்றன. தோற்றம் முகவரியை புறக்கணிக்கப் பயன்படுகிறது.
|
||||
ignore_origin_rules_definition_description: திருப்பிவிடப்பட்ட பிறகு ஒரு மூல முகவரியைத் தானாகவே புறக்கணிக்க அவை வாலபாக் பயன்படுத்துகின்றன. <br /> புதிய நுழைவைப் பெறும்போது ஒரு வழிமாற்றல் ஏற்பட்டால், அனைத்து புறக்கணிப்பு மூல விதிகளும் (<i> பயனர் வரையறுக்கப்பட்ட மற்றும் உதாரணமாக வரையறுக்கப்பட்ட </i>) மூல முகவரியைப் புறக்கணிக்கப் பயன்படுத்தப்படும்.
|
||||
how_to_use_them_title: அவற்றை எவ்வாறு பயன்படுத்துவது?
|
||||
how_to_use_them_description: «<i> rss.example.com </i>» (<i> திருப்பி விடப்பட்ட பிறகு, உண்மையான முகவரி எடுத்துக்காட்டு.காம் </i> என்பதிலிருந்து வரும் நுழைவின் தோற்றத்தை நீங்கள் புறக்கணிக்க விரும்புகிறீர்கள் என்று வைத்துக் கொள்வோம்) .
|
||||
how_to_use_them_description: «<i> rss.example.com </i>» என்பதிலிருந்து வரும் நுழைவின் தோற்றத்தை நீங்கள் புறக்கணிக்க விரும்புகிறீர்கள் என்று வைத்துக் கொள்வோம்(<i> திருப்பி விடப்பட்ட பிறகு, உண்மையான முகவரி எடுத்துக்காட்டு.காம் </i>). <br />அவ்வாறான நிலையில், நீங்கள் <i> விதி </i> புலத்தில் «புரவலன் =" rss.example.com "ஐ வைக்க வேண்டும்.
|
||||
variables_available_title: விதிகளை எழுத எந்த மாறிகள் மற்றும் ஆபரேட்டர்கள் நான் பயன்படுத்தலாம்?
|
||||
variables_available_description: 'புறக்கணிப்பு தோற்றம் விதிகளை உருவாக்க பின்வரும் மாறிகள் மற்றும் ஆபரேட்டர்கள் பயன்படுத்தப்படலாம்:'
|
||||
meaning: பொருள்
|
||||
@ -156,7 +156,7 @@ config:
|
||||
operator_description:
|
||||
label: ஆபரேட்டர்
|
||||
equal_to: சமமாக…
|
||||
matches: ஒரு <i> பொருள் </i> ஒரு <i> தேடல் </i> (வழக்கு-கவர்ச்சியான) உடன் பொருந்துகிறது. foobar /.*"</ குறியீடு>
|
||||
matches: 'ஒரு <i> பொருள் </i> ஒரு <i> தேடல் </i> (வழக்கு-கவர்ச்சியான) உடன் பொருந்துகிறது. <br />எடுத்துக்காட்டு: <code> _all~ "https?://rss.example.com/foobar/.*"</code>'
|
||||
otp:
|
||||
page_title: இரண்டு காரணி ஏற்பு
|
||||
app:
|
||||
@ -233,13 +233,14 @@ import:
|
||||
elcurator:
|
||||
page_title: இறக்குமதி> எல்குரேட்டர்
|
||||
description: இந்த இறக்குமதியாளர் உங்கள் எல்குரேட்டர் கட்டுரைகள் அனைத்தையும் இறக்குமதி செய்வார்.
|
||||
how_to: உங்கள் எல்குரேட்டர் ஏற்றுமதியைத் தேர்ந்தெடுத்து பதிவேற்றவும் இறக்குமதி செய்யவும் கீழே உள்ள பொத்தானைக் சொடுக்கு செய்க.
|
||||
readability:
|
||||
page_title: இறக்குமதி> வாசிப்பு
|
||||
description: இந்த இறக்குமதியாளர் உங்கள் வாசிப்பு கட்டுரைகள் அனைத்தையும் இறக்குமதி செய்வார்.
|
||||
how_to: உங்கள் வாசிப்பு ஏற்றுமதியைத் தேர்ந்தெடுத்து பதிவேற்றவும் இறக்குமதி செய்யவும் கீழே உள்ள பொத்தானைக் சொடுக்கு செய்க.
|
||||
worker:
|
||||
enabled: 'இறக்குமதி ஒத்திசைவற்ற முறையில் செய்யப்படுகிறது. இறக்குமதி பணி தொடங்கப்பட்டதும், ஒரு வெளிப்புற தொழிலாளி ஒரு நேரத்தில் வேலைகளை கையாள்வார். தற்போதைய சேவை:'
|
||||
download_images_warning: உங்கள் கட்டுரைகளுக்கான படங்களை பதிவிறக்குவதற்கு உதவினீர்கள். கிளாசிக் இறக்குமதியுடன் இணைந்து, தொடர பல ஆண்டுகள் ஆகலாம் (அல்லது தோல்வியுற்றிருக்கலாம்). பிழைகளைத் தவிர்க்க ஒத்திசைவற்ற இறக்குமதியை இயக்குவதற்கு நாங்கள் <strong> கடுமையாக பரிந்துரைக்கிறோம் </strong.
|
||||
download_images_warning: உங்கள் கட்டுரைகளுக்கான படங்களைப் பதிவிறக்குவதற்கு உதவினீர்கள். கிளாசிக் இறக்குமதியுடன் இணைந்து, தொடர பல ஆண்டுகள் ஆகலாம் (அல்லது தோல்வியுற்றிருக்கலாம்). பிழைகளைத் தவிர்க்க ஒத்திசைவற்ற இறக்குமதியை இயக்குவதற்கு நாங்கள் <strong> கடுமையாகப் பரிந்துரைக்கிறோம் </strong>.
|
||||
firefox:
|
||||
page_title: இறக்குமதி> பயர்பாக்ச்
|
||||
description: இந்த இறக்குமதியாளர் உங்கள் பயர்பாக்ச் புக்மார்க்குகளை இறக்குமதி செய்வார். உங்கள் புக்மார்க்குகளுக்கு (Ctrl+Shift+O), பின்னர் "இறக்குமதி மற்றும் காப்புப்பிரதி" க்குச் செல்லுங்கள், "காப்புப்பிரதி…" என்பதைத் தேர்வுசெய்க. நீங்கள் ஒரு சாதொபொகு கோப்பைப் பெறுவீர்கள்.
|
||||
@ -260,6 +261,14 @@ import:
|
||||
page_title: இறக்குமதி> பாக்கெட் உஉகுமொ
|
||||
description: இந்த இறக்குமதியாளர் உங்கள் அனைத்து பாக்கெட் புக்மார்க்குகளையும் (HTML ஏற்றுமதி வழியாக) இறக்குமதி செய்வார். Https://getpocket.com/export க்குச் சென்று, பின்னர் உஉகுமொ கோப்பை ஏற்றுமதி செய்யுங்கள். ஒரு உஉகுமொ கோப்பு பதிவிறக்கம் செய்யப்படும் ("RIL_EXPORT.HTML" போன்றவை).
|
||||
how_to: புக்மார்க்கு காப்புப்பிரதி கோப்பைத் தேர்ந்தெடுத்து அதை இறக்குமதி செய்ய கீழே உள்ள பொத்தானைக் சொடுக்கு செய்க. அனைத்து கட்டுரைகளையும் பெற வேண்டியிருப்பதால் செயல்முறை நீண்ட நேரம் ஆகலாம் என்பதை நினைவில் கொள்க.
|
||||
omnivore:
|
||||
how_to: தயவுசெய்து உங்கள் சர்வவல்லமையுள்ள ஏற்றுமதியை அவிழ்த்து, பின்னர் "மெட்டாடேட்டா_எக்ச்_.டி.இ_ஒய்.சோன்" என்ற பெயரில் ஒவ்வொரு சாதொபொகு கோப்பையும் ஒவ்வொன்றாக பதிவேற்றவும்.
|
||||
page_title: இறக்குமதி> சர்வவல்லவர்
|
||||
description: இந்த இறக்குமதியாளர் உங்கள் சர்வவல்லமையுள்ள அனைத்து கட்டுரைகளையும் இறக்குமதி செய்வார்.
|
||||
pocket_csv:
|
||||
description: இந்த இறக்குமதியாளர் உங்கள் பாக்கெட் புக்மார்க்குகள் (சி.எச்.வி ஏற்றுமதி வழியாக) இறக்குமதி செய்வார். Https://getpocket.com/export க்குச் சென்று, பின்னர் கோப்பை ஏற்றுமதி செய்யுங்கள். ஒரு சிப் கோப்பு பதிவிறக்கம் செய்யப்படும் ("பாக்கெட்.சிப்" போன்றவை). அதைப் பிரித்தெடுக்கவும், "Part_000000.CSV" எனப்படும் காபிம கோப்பைப் பெறுவீர்கள்.
|
||||
how_to: புக்மார்க்கு காப்புப்பிரதி கோப்பைத் தேர்ந்தெடுத்து அதை இறக்குமதி செய்ய கீழே உள்ள பொத்தானைக் சொடுக்கு செய்க. அனைத்து கட்டுரைகளையும் பெற வேண்டியிருப்பதால் செயல்முறை நீண்ட நேரம் ஆகலாம் என்பதை நினைவில் கொள்க.
|
||||
page_title: இறக்குமதி> பாக்கெட் கபிம
|
||||
developer:
|
||||
howto:
|
||||
description:
|
||||
@ -270,7 +279,7 @@ developer:
|
||||
paragraph_4: 'இப்போது, உங்கள் கிள்ளாக்கை உருவாக்கவும் (கிளையன்ட்_ஐடி, கிளையன்ட்_செக்ரெட், பயனர்பெயர் மற்றும் கடவுச்சொல்லை நல்ல மதிப்புகளுடன் மாற்றவும்):'
|
||||
paragraph_6: 'பநிஇ இறுதிப்புள்ளிக்கு அழைப்பு செய்ய அணுகல்_டோகன் பயனுள்ளதாக இருக்கும். உதாரணமாக:'
|
||||
paragraph_7: இந்த அழைப்பு உங்கள் பயனருக்கான அனைத்து உள்ளீடுகளையும் திருப்பித் தரும்.
|
||||
paragraph_8: எல்லா பநிஇ இறுதிப் புள்ளிகளையும் நீங்கள் காண விரும்பினால், எங்கள் பநிஇ ஆவணங்களுக்கு </a> க்கு <a href = "%இணைப்பு%"> ஐப் பார்க்கலாம்.
|
||||
paragraph_8: எல்லா பநிஇ இறுதிப் புள்ளிகளையும் நீங்கள் காண விரும்பினால், <a href="%link%">எங்கள் பநிஇ ஆவணங்களுக்கு </a> ஐப் பார்க்கலாம்.
|
||||
back: பின்
|
||||
page_title: பநிஇ கிளையண்ட்ச் மேனேச்மென்ட்> எனது முதல் பயன்பாட்டை எவ்வாறு உருவாக்குவது
|
||||
page_title: பநிஇ கிளையண்ட்ச் மேலாண்மை
|
||||
@ -382,6 +391,7 @@ flashes:
|
||||
tagging_rules_not_imported: குறிச்சொல் விதிகளை இறக்குமதி செய்யும் போது பிழை
|
||||
ignore_origin_rules_deleted: தோற்ற விதி நீக்கப்பட்டது
|
||||
ignore_origin_rules_updated: தோற்ற விதி புதுப்பிக்கப்பட்டது
|
||||
otp_code_invalid: தவறான இரண்டு காரணி அங்கீகாரக் குறியீடு
|
||||
entry:
|
||||
notice:
|
||||
entry_already_saved: நுழைவு ஏற்கனவே %தேதியில் சேமிக்கப்பட்டது %
|
||||
@ -608,7 +618,7 @@ howto:
|
||||
description: இந்த படிவத்திற்கு நன்றி
|
||||
browser_addons:
|
||||
firefox: பயர்பாக்ச் addon
|
||||
chrome: Chrome addon
|
||||
chrome: நிறமி செருகுநிரல்
|
||||
opera: ஓபரா துணை
|
||||
mobile_apps:
|
||||
android:
|
||||
@ -697,6 +707,7 @@ quickstart:
|
||||
github: கிட்அப்பில்
|
||||
email: மின்னஞ்சல் மூலம்
|
||||
gitter: கிட்டரில்
|
||||
matrix: மேட்ரிக்சில்
|
||||
tag:
|
||||
confirm:
|
||||
delete: '% பெயர் % குறிச்சொல்லை நீக்கவும்'
|
||||
@ -710,7 +721,7 @@ tag:
|
||||
add: கூட்டு
|
||||
placeholder: கமாவால் பிரிக்கப்பட்ட பல குறிச்சொற்களை நீங்கள் சேர்க்கலாம்.
|
||||
export:
|
||||
footer_template: '<div சூல் தண்டு = "text-align: மையம்;"> <p> %முறையுடன் வாலபாக் தயாரித்த </p> <p> தயவுசெய்து <a href = "https://github.com/wallabag/wallabag/issues ஐத் திறக்கவும் "> ஒரு சிக்கல் </a> உங்கள் சாதனத்தில் இந்த மின் புத்தகத்தின் காட்சியில் சிக்கல் இருந்தால். </p> </viv>'
|
||||
footer_template: '<div style="text-align:center;"> <p> %முறையுடன் வாலபாக் தயாரித்த </p> <p> தயவுசெய்து <a href="https://github.com/wallabag/wallabag/issues"> ஒரு சிக்கல் </a>ஐத் திறக்கவும், உங்கள் சாதனத்தில் இந்த மின் புத்தகத்தின் காட்சியில் சிக்கல் இருந்தால். </p> </div>'
|
||||
unknown: தெரியவில்லை
|
||||
site_credential:
|
||||
page_title: தள நற்சான்றிதழ் மேலாண்மை
|
||||
|
||||
Reference in New Issue
Block a user