Compare commits

..

991 commits

Author SHA1 Message Date
9107b058f5 Traits are no longer covered explicitly 2024-12-27 23:58:49 -05:00
f066aa6649 Method coverage no longer works in PHPUnit 11 2024-12-27 23:39:44 -05:00
2aa4fc9215 Convert method coverage 2024-12-27 21:43:44 -05:00
ba4d3f497b Replace PHPUnit annotations with attributes 2024-12-27 20:28:38 -05:00
ba42b7b0c1 Upgrade to PHPUnit 11
Deprecated annotations still need to be addressed
2024-12-27 19:58:07 -05:00
835f6fb998 Update Daux to latest version 2024-12-27 16:04:41 -05:00
7966947022 Update changelog 2024-12-27 13:59:38 -05:00
7f38369935 Address problem with MySQL 9 2024-12-27 13:36:54 -05:00
9e5c841377 Adjust MySQL for PHP 8.4 2024-12-27 13:12:34 -05:00
b306f0fc29 Crash was related to xdebug 2024-12-27 09:45:22 -05:00
9c25b7b6e6 Test cleanup 2024-12-25 10:11:28 -05:00
1f137ba710 Work around segmentation fault 2024-12-25 08:24:12 -05:00
93c322bdfa Correct most remaining test bugs 2024-12-24 22:58:58 -05:00
f1d3055f4c Convert remaining usage of Phony to Phake 2024-12-24 14:50:09 -05:00
1cd3f29fe3 Merge branch 'master' into phake 2024-12-15 22:38:22 -05:00
2d7b2dde39 Fix-up laminas-xml and docopt
Now there's only Phony, which will be replaced with Phake.
2024-12-15 21:53:20 -05:00
e8be7d0f38 Address our own deprecations in PHP 8.4 2024-12-15 16:31:57 -05:00
1ae3c33344 Be more specific about coverage of Database class
This has uncovered a bug with nextFetch calculation, it seems. Fixing it is deferred for now.
2024-07-10 16:28:19 -04:00
aed3749da8 Shore up coverage 2024-07-09 14:58:31 -04:00
b68a68b500 Update changelog 2024-07-06 12:28:41 -04:00
ade1c79361 Fix language-loading loop by applying incrementally 2024-07-06 12:24:16 -04:00
0170ec19c7 Fix language-loading infinite loop when throwing exceptions
This loop has existed since the very beginning, and was only ever papered over instead of actually fixing it.
2024-07-06 11:01:00 -04:00
8834a65e4c Address case where user is already arsse 2024-01-10 11:00:08 -05:00
b1154359e4 Prepare release 2024-01-09 19:04:28 -05:00
3838cdc0af Merge branch 'arch2' 2024-01-09 18:42:55 -05:00
1f9beb85a5 Fix socket path 2024-01-09 18:13:36 -05:00
2670ed4ab8 Typo 2024-01-07 12:33:58 -05:00
4df97eefcb Oops 2024-01-07 12:30:40 -05:00
82f94d1dae Remove obsolete systemd patching 2024-01-07 12:28:47 -05:00
7f6b36d4da Remove references to Pandoc 2024-01-07 11:23:46 -05:00
06ec67816a Update arsse-git PKGBUILD
This includes both adaptations for php-legacy as well as for the new mdoc manual
2024-01-05 11:20:56 -05:00
e647d79e1e Update dependencies 2024-01-05 09:17:08 -05:00
f853851568 Run with the correct executable name visible to PHP 2024-01-05 09:16:57 -05:00
4131531bff Tweaks
- Add new files to PKGBUILD backup
- Adjust Robo caller to use ARSSE_PHP variable; Windows will need adjusting later
2023-12-29 19:42:33 -05:00
9086a5d9b1 Add interpreter adaptation for service, with documentation 2023-12-24 14:39:42 -05:00
cce48878e7 Add pool for php-legacy 2023-12-22 16:35:09 -05:00
185ae88ba3 Correct outdated documentation 2023-12-22 13:47:26 -05:00
29fb134633 First steps in supporting php-legacy in Arc
This sorts out HTTP servers, hopefully. Adaptation for systemd and documentation are still required
2023-12-22 13:27:02 -05:00
72dd21686e Convert user manager tests to Phake 2023-11-20 16:39:58 -05:00
9d92c1661d More Phake conversion 2023-11-11 17:22:13 -05:00
7e58dd800d Remove list uses of PHPUnit mocks 2023-11-09 23:35:04 -05:00
ece19494d7 Fix errors and failures 2023-11-09 23:31:22 -05:00
1a981bf267 Start on conversion back to Phake 2023-11-09 22:15:11 -05:00
9951b44932 Update changelog 2023-11-01 12:48:30 -04:00
692af39768 Use mdoc manual directly without any preprocessing 2023-10-29 22:20:14 -04:00
73446887f5 Editorial tweak 2023-10-28 08:06:49 -04:00
e20937f98f The rest of the mdoc manual 2023-10-27 23:23:15 -04:00
2ff3286aa9 More work on mdoc manual 2023-10-27 20:44:01 -04:00
174ed544b2 Start on an mdoc-format manual 2023-10-27 12:28:22 -04:00
5579144fee Complete native groff manual 2023-10-26 18:15:20 -04:00
7613142221 Start on a native groff manual 2023-10-25 17:24:54 -04:00
d9b90390e7 Update style rules 2023-06-17 11:09:03 -04:00
59a9329032 Upgrade dependencies where possible with PHP 7.3 2023-03-24 10:28:20 -04:00
be3adf7026 Document RoboFile better 2023-03-23 11:31:33 -04:00
eb371b75fe Fix documentation errors 2023-03-22 23:45:56 -04:00
1b80ad37bc Merge branch 'csfixer3'
Code style cleanup to mostly conform to PSR-12
2023-03-22 23:33:05 -04:00
3c83fc9139 Update php-cs-fixer rules 2023-03-22 23:14:19 -04:00
711f87aad8 Housekeeping
- Update Docopt
- Switch toin-house Picofeed branch for now
- Update composer-bin
- Update php-cs-fixer

Daux has been left as-is for now even though we're using an old version
2023-03-22 22:57:58 -04:00
0a8d19d37d Require PHP 7.3
This addresses the last of the deprecation warnings in PHP 8.2
2023-01-28 12:43:42 -05:00
fe06ffc176 Avoid dynamic property creation with PicoFeed
This only leaves the Laminas XML deprecated behaviour to handle
2023-01-28 11:18:14 -05:00
0d6f8d2921 Avoid most deprecation warnings
The Feed class sets dynamic properties on Picofeed classes; this will
need to be completely rewritten. Version 1.2 of the Laminas XML class
also uses a deprecated function, but upgrading it to 1.3 will require
PHP 7.3.
2023-01-27 15:33:34 -05:00
92b1a840a1 Support PHP 8.2 properly 2023-01-24 15:12:21 -05:00
a25e777ec6 Version bump 2022-09-14 08:06:22 -04:00
44e2c9c13e Update documentation 2022-09-13 19:56:01 -04:00
866800dcc5 Finish last Guzzle-related tests 2022-09-13 19:52:29 -04:00
136d3782e3 Update changelog 2022-08-07 20:16:27 -04:00
3be3f43bab Start on tests for response wrappers 2022-08-06 22:59:25 -04:00
d2f3f19128 Fix failures 2022-08-06 16:16:18 -04:00
459e44e041 Address remaining errors
Still many failures to fix
2022-08-06 16:03:50 -04:00
56f015bfb9 More Guzzle conversion 2022-08-06 13:40:02 -04:00
64ec3f6ae4 Use unused variable 2022-08-05 22:10:36 -04:00
4d18bf27e2 Adjust most uses of Diactoros to Guzzle PSR-7 2022-08-05 22:08:36 -04:00
e588a52e88 Replace ServerRequestFactory 2022-08-04 22:15:43 -04:00
6c0183faea Replace instances of Diactoros' EmptyResponse 2022-08-04 22:04:39 -04:00
560d4db139 Remove Diactoros in favour of Guzzle PSR-7
For now this only adds convenience wrappers around Guzzle to somewhat
emulate Diactoros (albeit with a different API). Code and tests will be
adjusted in due course.
2022-08-04 09:26:17 -04:00
2557c22410 Update dependencies 2022-06-22 15:33:17 -04:00
4ca7b65a65 Update dependencies 2022-06-09 21:21:17 -04:00
4d37ae30ae Update dependencies
Addresses a Guzzle vulnerability, though it does not affect The Arsse
2022-06-05 21:38:08 -04:00
d1da6fbe5e Use cases rather than casting bools to int in SQL 2022-05-30 17:29:34 -04:00
d54733ad98 Update link to Nextcloud News documentation again
The URL has changed again since it was updated two weeks ago
2022-05-02 10:48:52 -04:00
a0c31fac5d Merge branch 'reader' 2022-04-30 17:34:29 -04:00
59358ec35b More PHP 7 fixes 2022-04-30 17:11:18 -04:00
90b66241b3 Fixes for PHP 7 2022-04-30 13:50:35 -04:00
761b3d5333 Return removed articles correctly in Miniflux 2022-04-29 23:28:47 -04:00
d64dc751f9 Tests for query filters 2022-04-29 20:53:05 -04:00
f51acb4264 Build exceptions correctly in Miniflux for clarity 2022-04-29 19:10:11 -04:00
300225439c Fix trivial error in Miniflux
This is not a bug as the behaviour that should have been implemented was
not being relied upon
2022-04-29 19:04:08 -04:00
c6cc2a1a42 Restore coverage for Query class 2022-04-29 17:23:41 -04:00
a44fe103d8 Prototype for nesting query filters 2022-04-29 16:37:16 -04:00
630536d789 Tests for union context 2022-04-29 16:35:46 -04:00
206c5c0012 Fill in union context 2022-04-28 22:32:10 -04:00
0c8f33c37c Remove setCTE and pushCTE from query builder 2022-04-28 21:24:57 -04:00
26e431b1a5 Simplify more queries 2022-04-28 17:57:31 -04:00
336207741d Add missing API documentation 2022-04-28 17:37:10 -04:00
6863c182d7 Update reference to the "Reeder" client 2022-04-27 10:22:43 -04:00
f2aad7188c Update links to TT-RSS documentation 2022-04-27 10:16:20 -04:00
65b1bb4fcd Allow multiple dates in TT-RSS searches 2022-04-26 17:13:16 -04:00
2c5b9a6768 Fix missing TTRSS coverage 2022-04-26 12:13:15 -04:00
17832ac63e Allow timezone in TT-RSS search queries
Does not quite work yet
2022-04-25 22:28:16 -04:00
e65069885b Clean up obsolete FIXMEs 2022-04-25 18:30:13 -04:00
7e5d8494c4 Tests for selecting arrays of ranges 2022-04-25 14:33:19 -04:00
e6505a5fda Work around possible MySQL bug 2022-04-25 09:56:13 -04:00
2acacd2647 Implement handling for arrays of ranges
Multiple ranges of articles or editions were not implemented, but the
functionality is generic and could be extended if later needed.
2022-04-24 20:13:08 -04:00
f6799e2ab1 Tests for date ranges in contexts 2022-04-24 12:25:37 -04:00
33a3478a58 Avoid use of PHP 7.4 feature 2022-04-23 17:24:25 -04:00
2489743d0f Further simplifications 2022-04-23 13:21:52 -04:00
0bd01849bb Remove unnecessary in() clause 2022-04-23 11:51:53 -04:00
895c045c9b Simplify folder selection in article queries 2022-04-23 11:15:57 -04:00
fe02613214 Fix coverage 2022-04-22 22:46:13 -04:00
427bddd3b7 Allow multiple date ranges 2022-04-22 20:09:07 -04:00
53ba591720 Finish up article selection refactor 2022-04-22 19:22:50 -04:00
97dfef3267 Fix typos 2022-04-21 23:30:19 -04:00
396ca86482 Start on removal of conditional CTEs
This breaks the code for now, but will make clearer queries once done
2022-04-21 23:19:19 -04:00
4a87926dd5 Fix up context tests 2022-04-21 14:37:28 -04:00
6f1332c559 Start to shore up testing 2022-04-20 19:11:04 -04:00
308b592b18 Clean up coontext classes 2022-04-19 23:20:20 -04:00
983fa58ec8 Convert article and edition ranges to atomic
Unit tests for ranges are still missing
2022-04-19 22:53:36 -04:00
2c2bb4a856 Retrofits dates to use ranges
Article and edition ranges still need work
2022-04-19 20:19:51 -04:00
c993168002 Update URL of Nextcloud News documentation 2022-04-19 19:33:35 -04:00
73497688fc Break contexts up into traits
This will make their expansion easier and will also be useful for using
typed properties later
2022-04-18 22:04:48 -04:00
1b0256d6ce Abandon automation of binary packaging for now 2022-04-04 14:19:53 -04:00
144a41e061 Prepare new version 2022-04-04 14:05:04 -04:00
60b4002329 Revert "Document that we actually emulate Miniflux 2.0.29"
This reverts commit d379aa2253.
2022-04-04 13:43:20 -04:00
f24ec8b00b Address security vulnerability in Guzzle's PSR-7
implementation, used by PicoFeed
2022-04-04 13:40:39 -04:00
d379aa2253 Document that we actually emulate Miniflux 2.0.29
At the time The Arsse 0.9.0 was released the new feature in 2.0.29 was
already implemented, but that version of Miniflux had not been released.
2022-03-14 13:16:46 -04:00
b707ecc942 Tag new version 2022-01-17 19:31:06 -05:00
afe26fb8e1 Style fixes 2022-01-14 19:28:42 -05:00
3a219a591d Update dependencies 2022-01-14 16:27:01 -05:00
b5579d6e43 Support PHP 8.1 2022-01-11 17:54:02 -05:00
b660508009 Improve MySQL test performance 2021-12-30 18:22:50 -05:00
3c884f521b Update dependencies 2021-10-29 14:31:23 -04:00
70b063e028 Make parts of generic packaging conditional
This should allow to rebuild old releases from before Debian packages,
Arch ppackages, manpages, or even the HTML manual were added
2021-07-17 07:12:24 -04:00
cf3d270077 Merge branch 'deb' 2021-07-17 00:20:28 -04:00
1fa75aba4a Generate Debian source package without deb tooling 2021-07-17 00:19:55 -04:00
317d23c1bb Fix copy-paste error in manual 2021-07-12 10:30:53 -04:00
75dbe380ba Add Pandoc to AUR arsse-git build dependencies 2021-07-11 20:33:56 -04:00
08250841a0 Don't sign packages 2021-07-11 17:25:14 -04:00
2452264893 Date release 2021-07-11 17:12:05 -04:00
c1e57eb01f Add manual installation instructions 2021-07-11 13:45:56 -04:00
e75e0dfd2f Clarify exactly what "older Debians" is
This is relevant because Debian 11 (Bullseye) is not yet released
2021-07-11 10:58:33 -04:00
6c11c9e485 Fix Debian bugs 2021-07-10 22:47:34 -04:00
40a2856ae8 Use correct PHP_FPM socket paths for Debian 2021-07-10 10:02:59 -04:00
c7dcc36ba1 Update version 2021-07-09 22:41:00 -04:00
a31fb896d9 Update changelog 2021-07-09 21:57:16 -04:00
def4a3bc77 Move Debian adaptations to Debian packaging rules
Also add lintian to pbuilder packages
2021-07-09 20:45:52 -04:00
cd89472575 Fix up Debian documentation 2021-07-09 17:43:44 -04:00
beea98040c Initial documentation for Debian packages 2021-07-09 17:32:59 -04:00
59ff88f3b6 Add all-in-one packaging task 2021-07-08 23:11:58 -04:00
4070870421 Enforce external tooling requirements 2021-07-08 23:11:58 -04:00
5ab9dc3840 Add missing pbuilder script 2021-07-08 23:07:59 -04:00
20ffb2484a Simply Robo input for Arch and Deb packaging 2021-07-08 18:04:15 -04:00
f91b3c0120 Output packages to a "release" directory
Further fixups still needed for Arch
2021-07-08 15:55:59 -04:00
4121fc3e21 Database server ports must be integers 2021-07-07 16:19:01 -04:00
7ad3611a84 Set up configuration file for Debian properly 2021-07-07 16:03:34 -04:00
5412eb348f Clean up maintainer scripts 2021-07-07 15:24:45 -04:00
c936ecc1af Fix another typo 2021-07-07 15:04:03 -04:00
99c923b1b1 Fix typos 2021-07-07 14:57:06 -04:00
c2237532eb Add glue for dbconfig-common configuration 2021-07-07 14:51:16 -04:00
9687ce026e Add MySQL back to Debian depeendencies
Recent Debian seems to favour MySQL over MariaDb again,
removing the compatibility problem
2021-07-07 11:50:24 -04:00
30bed8a9d5 Typo 2021-07-07 11:27:41 -04:00
cf9059c2b0 Update tooling 2021-07-07 11:26:35 -04:00
93bcf93685 Prototype Debian maintainer scripts 2021-07-07 08:22:19 -04:00
46e20be983 Test for service reloading 2021-07-06 10:07:56 -04:00
ad32bf3340 Style fixes 2021-07-05 21:47:44 -04:00
b8ac646d22 Fix up hangup signal handling 2021-07-05 20:57:05 -04:00
37c58e186a Handle hangup signal 2021-07-05 20:57:05 -04:00
88fe3e76cb Fix up missing-extension message 2021-07-05 20:57:05 -04:00
3c8ee42666 Basic tests for exception checking 2021-07-05 20:57:05 -04:00
04adc3b997 Document forking in the manpage 2021-07-05 20:57:05 -04:00
c49cb72528 Fail gracefully when extensions are missing
This still needs tests
2021-07-05 20:57:05 -04:00
c9a2393a4e Note requirement for filter extension 2021-07-05 20:57:05 -04:00
75e87f31a0 Prototype code to check for missing extensions
This is useful in general, but will also provide clear error text if
trying to fork without the posix or pcntl extensions
2021-07-05 20:57:05 -04:00
3b51d4daea Fix license for Debian package 2021-07-05 20:57:05 -04:00
ce9dfc3f30 Add init script to Debian files 2021-07-05 20:57:05 -04:00
cbc7cd8ea7 Add an explicit path to init script 2021-07-05 20:57:05 -04:00
2e29f3f76e Correct typo 2021-07-05 20:57:05 -04:00
8a1a1eee42 Prototype init script
It should work, but testing it is not altogether obvious
2021-07-05 20:57:05 -04:00
e160189224 Handle exceptions from child processes 2021-07-05 20:57:05 -04:00
e9394e8599 More forking tweaks 2021-07-05 20:57:05 -04:00
577356cd3d Fork error test 2021-07-05 20:57:05 -04:00
514cb0a351 Ow 2021-07-05 20:57:05 -04:00
0bb5e916d2 Test PID writing 2021-07-05 20:57:05 -04:00
2767ab755e Use D modifier in pattern 2021-07-05 20:57:05 -04:00
a4036afbf8 Partial tests for PID file reading 2021-07-05 20:57:05 -04:00
32c9d761c3 Clean up more exceptions 2021-07-05 20:57:05 -04:00
5b3e8fbef0 Refine some exceptions 2021-07-05 20:57:05 -04:00
b9fd9ac32e Tweaks 2021-07-05 20:57:05 -04:00
23749b51aa Tests for path resolution 2021-07-05 20:57:05 -04:00
dfaf44ac68 Basic path resolution tests 2021-07-05 20:57:05 -04:00
bab64add9b Separate PID conflict checking from PID claiming 2021-07-05 20:57:05 -04:00
822158d1bd Update dependencies 2021-07-05 20:57:05 -04:00
f1c29c99c7 Finish testing PID file path checking 2021-07-05 20:57:05 -04:00
59cf27089a More daemon cleanup 2021-07-05 20:57:05 -04:00
4e1193bab2 Move forking daemon support code to own class 2021-07-05 20:57:05 -04:00
32e04e3938 Move forking and related to Service class 2021-07-05 20:57:05 -04:00
55acb87577 Start on PI(D file resolution tests 2021-07-05 20:57:05 -04:00
e8cab78bd6 Handle last possible PID failures
Opening the PID file can still fail separately, though this is unlikely
2021-07-05 20:57:05 -04:00
9595c4f019 Start filling out PID file exceptions 2021-07-05 20:57:05 -04:00
372bf9f630 Exclude code from coverage 2021-07-05 20:57:05 -04:00
4ffc29781d Remove references to oldpass param 2021-07-05 20:57:05 -04:00
47af739e47 Catch more PID path failures 2021-07-05 20:57:05 -04:00
2c7b16ed27 Respond to termination signals and delete PID file 2021-07-05 20:57:05 -04:00
410310282f Load configuration after forking 2021-07-05 20:57:05 -04:00
29b83b4453 Prototype forking daemon 2021-07-05 20:57:05 -04:00
fc2abc1203 Use D modifier for all patterns with $ anchors 2021-06-24 11:58:50 -04:00
59c5c2eb14 Oops 2021-05-31 13:11:22 -04:00
3cd3ac4a51 Correct filename conflict 2021-05-31 13:10:16 -04:00
837895fd6a Adapt dist files for Debian 2021-05-31 12:12:56 -04:00
b4c9413130 Update README 2021-05-30 17:14:01 -04:00
bafb788b02 Correct errors in manual 2021-05-30 15:51:18 -04:00
68e3cd82ca Don't include section number in title 2021-05-30 10:04:33 -04:00
c3fa4788d6 Use proper metadata block for manpage 2021-05-30 09:48:10 -04:00
3567f294a6 Merge branch 'manpage' 2021-05-30 09:22:10 -04:00
8c0f047747 Update HTML manual to mention man page 2021-05-30 09:19:08 -04:00
fd76b1b611 Add examples to manual page 2021-05-29 23:37:02 -04:00
4317a96db1 Work around double spacing 2021-05-29 22:45:13 -04:00
62d49e0d3c Fill out most of the manual page
Removed most of the online help as a consequence since maintaining
both is frought
2021-05-29 21:48:02 -04:00
88487d27a2 Expand manual page 2021-05-29 17:40:20 -04:00
46c88f584f Fix copying of man page in PKGBUILDs 2021-05-29 16:29:53 -04:00
92823d5bc2 Create directories before executing Pandoc 2021-05-29 16:19:52 -04:00
3e55ab3849 Move man pages to their own directory 2021-05-29 15:44:51 -04:00
2ec7acc50b Turn off "smart" character substitution in Pandoc 2021-05-29 14:13:45 -04:00
d3a983e7f0 Move the markdown manpage
Daux uses Cmmonmark, which does not support indention, required for
proper formatting of manual pages. Consequently, the manul page will
instead be standalone.
2021-05-29 14:05:30 -04:00
176aac0ad7 Fix stupid typo properly 2021-05-29 12:37:32 -04:00
e439dd8277 Fix manpage in Arch PKGBUILD 2021-05-29 12:26:51 -04:00
6cc9f96728 Prototype manual page 2021-05-29 11:13:19 -04:00
d4569c77a9 Add database location to tmpfiles 2021-05-29 11:09:11 -04:00
add1acc87a Fix more lintian complaints 2021-05-28 16:23:42 -04:00
14d3cdfe58 Hopefully fix some Debian problems 2021-05-28 12:33:52 -04:00
281760be71 Address some lintian complaints 2021-05-28 08:29:49 -04:00
758a02d667 Move generic configuration file 2021-05-27 19:39:53 -04:00
18846c19cb Add install list for Debian package 2021-05-27 19:00:29 -04:00
b5bbdc2bc6 Date release 2021-05-25 17:22:48 -04:00
3be6c9984d Update Apache documentation in manual 2021-05-25 17:16:40 -04:00
6c84b2199e More Apache fixes 2021-05-25 17:03:08 -04:00
f9cbac2c31 Hopefully fix Apache configuration 2021-05-25 15:13:18 -04:00
32ca0c3fe4 Appease GitHub once and for all 2021-05-24 19:22:37 -04:00
a81bd0e45c Add whitespace 2021-05-24 14:51:21 -04:00
86d82a2586 Use global flag when replacing with sed 2021-05-24 14:50:21 -04:00
f0bf55f9cf Add ExecStart to parent systemd unit 2021-05-24 14:07:11 -04:00
1055611940 Add version constraints to Arch dependencies 2021-05-24 12:40:11 -04:00
2ccfb1fd33 Fix packaging process 2021-05-23 22:03:47 -04:00
9eabfd0f27 Fix up sed usage in PKGBUILD 2021-05-23 19:01:51 -04:00
0236b42052 Use tmpfiles to create link to config file 2021-05-23 17:57:50 -04:00
8aa9d81fd1 Update changelog 2021-05-23 12:46:13 -04:00
11fc83da60 Significant edits to the manual 2021-05-23 12:24:42 -04:00
de55290746 Fix build dependencies for Deb package 2021-05-22 15:05:31 -04:00
f844c17a94 More Debian fixes 2021-05-22 07:16:48 -04:00
0de9647809 Add compat file 2021-05-21 22:03:40 -04:00
e653fb3f73 Enhancements to Debian files 2021-05-21 21:11:22 -04:00
b7909d7cd3 Downgrade tool dependencies for Ubuntu 2021-05-21 15:13:23 -04:00
3c9f4dd66f Prototype Debian rules file 2021-05-21 12:51:20 -04:00
3537e74d49 Update dependencies 2021-05-21 12:11:50 -04:00
d031d931a5 Tidy up the Robo file further 2021-05-21 06:43:17 -04:00
38cb1059b2 Shorten output of packaging task 2021-05-20 23:53:25 -04:00
16174f11b6 Add changelog parsing to packaging task 2021-05-20 23:38:03 -04:00
073f6b3c39 Prototype Debian control file and other changes 2021-05-20 17:47:02 -04:00
3f3f449da1 Re-organize manual 2021-05-20 10:20:32 -04:00
2260b7cc50 Back up all Web server configuration 2021-05-20 10:20:08 -04:00
61eb4a252e Fix doc URLs 2021-05-20 09:05:00 -04:00
3f401f1cfa Fix typo 2021-05-19 23:10:01 -04:00
6c750d2dc0 Documentation for installing on Arch
Documentations for Debian still needs to be amended
2021-05-19 23:03:10 -04:00
6d790c5efd Add prototype for new Apache configuration
Needs testing
2021-05-19 18:59:20 -04:00
3a3b9231df Use generic configuration where possible 2021-05-19 15:06:37 -04:00
fbe03a2534 Use chmod instead of touch 2021-05-19 11:34:54 -04:00
e75f8cebfb Add Arch packaging to Robo file 2021-05-19 11:27:21 -04:00
79391446cd Start moving Arch build responsibility to Robo
Also clean up the generic packaging task
2021-05-19 08:51:17 -04:00
19ab9df063 Fix more bugs 2021-05-18 18:42:42 -04:00
568b12600b Drop privileges when executing CLI 2021-05-18 09:46:42 -04:00
488af80a85 Update changelog 2021-05-18 09:44:52 -04:00
44612cfe8f Add tmpfiles 2021-05-17 21:43:52 -04:00
d1fd6e9653 Correct permissions
A tmpfiles.d configuration is still required
2021-05-17 20:08:32 -04:00
a97ca23631 Don't try to enable extensions 2021-05-17 18:03:47 -04:00
e2b182ebe6 Fix errors in Arch config file 2021-05-17 15:47:26 -04:00
3eab5aad5d Fix adding users to a blank database 2021-05-17 15:46:46 -04:00
805a508ea6 Use correct state path 2021-05-17 09:30:10 -04:00
3ebc23ab13 Tweaks 2021-05-17 07:27:22 -04:00
7abdf05b7f Make package from local files for now 2021-05-16 23:06:49 -04:00
971c12ff9f Rename sample to example 2021-05-16 20:21:41 -04:00
febc7c7ca4 Add configuration for Nginx 2021-05-16 18:44:42 -04:00
edb146b826 Use PHP-FPM instead of uWSGI 2021-05-16 15:59:52 -04:00
7ba4cabdde Prototype Arch PKGBUILD and supporting files
The package will be created, but the result itself has yet to be tested.
2021-05-15 22:26:06 -04:00
4080b2d09d Apply new rules 2021-04-14 11:17:01 -04:00
73731fa9db Fix up CS config file 2021-04-14 11:16:36 -04:00
18d296dcd6 Clean up CS fixer rules 2021-04-14 10:10:04 -04:00
114dcc568f Update dependencies 2021-04-14 09:50:25 -04:00
1331b14a04 Reverting for now 2021-04-09 18:18:42 -05:00
035feae0ce Removed postcss in favor of sass for building manual theme 2021-04-09 16:31:04 -05:00
8e063bea2f Appease GitHub again 2021-04-08 09:27:15 -04:00
4a9e66d872 Fix inconsistent grammar 2021-03-18 10:50:45 -04:00
fa4ab3218a Version bump 2021-03-18 10:45:28 -04:00
c4260323bc Answer 201 to PUTs like Miniflux
This does not apply to PUTs to /v1/entries, which were always 204
2021-03-18 10:38:20 -04:00
abc291460c Update Web server configuration in manual 2021-03-18 09:54:03 -04:00
bff3e21cd2 Date release 2021-03-06 16:41:07 -05:00
764b604edd Note Fiery Feeds' support for HTTP auth with Fever 2021-03-06 12:00:36 -05:00
4b0571299a Add results of client testing 2021-03-06 11:39:45 -05:00
2e4c57b75b Work around Microflux for Miniflux 2021-03-06 11:26:14 -05:00
dcb81ea043 Only provide icon ID when there is data 2021-03-05 19:31:11 -05:00
77a9bb801d Defer testing of Maxiflux 2021-03-05 08:02:14 -05:00
f90b78a976 Fix compatibility issues
- CORS OPTIONS requests may not ask for credentials
- Fever apparently didn't care about supplied Content-Type
2021-03-03 16:46:57 -05:00
e6b4edd160 Supress deprecation messages during runtime 2021-03-02 11:54:28 -05:00
8aca42c882 Minor fixes for correctness 2021-03-02 11:27:48 -05:00
458126416c Fix PostgreSQL coverage annotations 2021-03-02 11:11:04 -05:00
6b7257a6c4 Work around more MySQL stupidity 2021-03-02 11:04:42 -05:00
5cfa01f4d5 Work around MySQL stupidity 2021-03-02 11:04:21 -05:00
64ca5f1be0 Fix strict comparison failures 2021-03-01 23:27:58 -05:00
ed285ee28b Shut Robo up 2021-03-01 19:02:15 -05:00
4642b9fd1c PRovide upgrade path for assertRegExp 2021-03-01 18:20:50 -05:00
3a1fcaac39 Remove last uses of Phake 2021-03-01 18:01:25 -05:00
75148bfbc6 Convert NCNv1 tests to PHony 2021-03-01 10:03:52 -05:00
95812b8ba3 Convert last TT-RSS test
This is a particularly lazy one since I no longer understand how it
works—a problem for another day.
2021-03-01 09:37:56 -05:00
a322d034f3 More TT-RSS test conversions to Phony 2021-03-01 08:00:30 -05:00
2d951fb071 More TT-RSS test conversions 2021-02-28 12:07:53 -05:00
c7350c6d57 Convert Miniflux tests to using Phony 2021-02-28 09:23:38 -05:00
9dfe3919cf Progress on TT-RSS tests 2021-02-27 21:17:20 -05:00
e90aa585b2 PArtial rewrite of TT-RSS tests 2021-02-27 15:24:45 -05:00
2348786a92 Start on replacing Phake with Phony 2021-02-27 15:24:02 -05:00
9b369d902f Update tooling for PHP 8 2021-02-25 14:49:17 -05:00
dab4cb21e6 Geekttrss seems to work 2021-02-11 14:03:34 -05:00
d836d6a243 Add more clients to the untested list 2021-02-11 13:22:04 -05:00
50b2ca4500 Document tokens and metadata in the manual 2021-02-11 11:01:04 -05:00
9c0a3b7a57 Fix typo 2021-02-10 22:30:16 -05:00
3ba82b7c6d Fix CLI bootstrap problem 2021-02-10 22:12:00 -05:00
fa6d641634 Implement CLI for tokens 2021-02-10 21:40:51 -05:00
3795b1ccd8 Simplify CLI command processing 2021-02-10 12:46:28 -05:00
e8ed716ae6 Fix errors in CLI documentation 2021-02-10 12:11:28 -05:00
97d1de46f8 Fill in upgrade notes 2021-02-10 11:24:16 -05:00
68422390da Implement CLI for user metadata 2021-02-10 11:24:01 -05:00
b7c7915a65 Enforce admin rquirements in NCNv1 2021-02-09 10:05:44 -05:00
a760bf2ded Implement "t" and "f" booleans in TT-RSS 2021-02-09 09:37:31 -05:00
9ad4a37ddf Tests and fixes for Miniflux with PDO 2021-02-09 09:26:12 -05:00
687995c497 More potential Miniflux Web clints 2021-02-09 00:33:41 -05:00
29761d767a Update documentation 2021-02-08 23:52:13 -05:00
dad74c2616 Implement Fever icons 2021-02-08 23:51:40 -05:00
90034ac1f8 Style fixes 2021-02-08 19:14:11 -05:00
211cea648e Implement TT-RSS API level 15 2021-02-08 19:07:49 -05:00
f2e5d567ec Update sample Web server configuration 2021-02-07 21:38:16 -05:00
eae0ba4b68 Tests fortoken operations 2021-02-07 19:20:10 -05:00
9cc779a717 Import/export tests 2021-02-07 13:04:44 -05:00
37fd2ad4e9 Tests for new exception features 2021-02-07 09:07:53 -05:00
6c2de89f3e Revert copy-paste corruption 2021-02-06 23:55:40 -05:00
54a6fcc0d6 Consolidate object factoriesinto one place 2021-02-06 23:51:23 -05:00
a0d563e468 Update dependencies 2021-02-06 21:48:27 -05:00
b4ae988b79 Prototype OPML handling 2021-02-05 20:29:41 -05:00
681654f249 Documentation update 2021-02-05 09:22:10 -05:00
dd29ef6c1b Add feed refreshing stubs 2021-02-05 09:04:00 -05:00
ab1cf7447b Implement article marking 2021-02-05 08:48:14 -05:00
334a585cb8 Implement single-entry querying 2021-02-04 20:19:35 -05:00
a7d05a7717 Feed- and category-specific entry list routes 2021-02-04 17:52:40 -05:00
00ad1cc5b9 Last tests for article querying 2021-02-04 17:07:22 -05:00
d4a6909cf6 Positional article queries tests 2021-02-03 23:00:14 -05:00
e42e25d333 More article query tests 2021-02-03 16:27:55 -05:00
f7b3a473a9 Clarify ordering syntax rationale 2021-02-03 14:20:34 -05:00
af51377fe9 First set of article query tests 2021-02-03 13:06:36 -05:00
23ca6bb77b Count articles without offset or limit 2021-02-02 16:14:04 -05:00
0e7abfa8f9 Largely complete article querying
Tests to come
2021-02-02 16:05:16 -05:00
a43f8797c5 Add ability to sort by folder ID or name 2021-02-02 11:51:19 -05:00
ed27e0aaaa Sort nulls consistently
PostgreSQL normally sorts nulls after everything else in ascending order
and vice versa; we reverse this, to match SQLIte and MySQL
2021-02-02 10:00:08 -05:00
9d7ada7f59 Partial implementation of article sorting 2021-02-01 22:11:15 -05:00
007183450a Context and column list for article queries
Sorting and transformation still need to be figured out
2021-02-01 21:02:46 -05:00
197cbba77d Document article column definitions 2021-02-01 15:48:44 -05:00
ddbcb598e8 Match more closely Miniflux query string behaviour
- The starred key is a simople boolean whose value is immaterial
- Blank values are honoured for keys other than starred and status
2021-01-31 10:44:27 -05:00
bb89083444 Perform strict validation of query parameters
This is in fact stricter than Miniflux, which ignores duplicate values
and does not validate anything other than the string enumerations
2021-01-30 21:37:19 -05:00
1e924bed83 Partial query string normalization 2021-01-30 13:38:02 -05:00
3b2190ca10 Include folder names directly in subscription list 2021-01-28 14:55:18 -05:00
ad094f5217 Don't return icons without types at all 2021-01-27 13:41:10 -05:00
cd5f13f4b9 Tests for icon querying 2021-01-27 11:53:07 -05:00
76f1cc8e91 Adjust users of subscriptionIcon 2021-01-26 13:44:44 -05:00
cc2672fb0a Improve icon fetching interface 2021-01-26 12:03:26 -05:00
1eea3b3a4c Fix feed update test 2021-01-26 10:32:27 -05:00
8e749bb73c Report 404 on icons for absence of data
This is significant as upgraded databases have icon IDs, but no data
2021-01-25 09:02:52 -05:00
bdf9c0e9d2 Prototype feed icon querying 2021-01-24 21:53:45 -05:00
9197a8d08b Implement feed deletion 2021-01-24 21:12:32 -05:00
8eebb75b18 Implement feed editing 2021-01-24 20:28:00 -05:00
5a8a044a92 Implement single-feed querying 2021-01-24 13:54:54 -05:00
a646ad77b7 Use a read transaction when computing filter rules 2021-01-24 11:45:08 -05:00
cca4b205e4 Correct error output of getCategoryFeeds 2021-01-24 11:33:00 -05:00
a34edcb0d1 Last tests for feed creation 2021-01-24 11:25:38 -05:00
7893b5f59d More feed adding tests 2021-01-23 18:01:23 -05:00
36cc4928b9 Test feed fetching errors for Miniflux 2021-01-23 12:00:11 -05:00
727864f401 Implement feed listing by category
Also modify user list to reflect changes in Miniflux 2.0.27.
2021-01-22 18:24:33 -05:00
4972c79e32 Allow simpler feed exception creation 2021-01-21 22:44:22 -05:00
6936f365e4 Add calls coming in next version of Miniflux 2021-01-21 11:11:25 -05:00
fd25be5c27 Basic tests for feed creation 2021-01-20 18:28:51 -05:00
e7b2f54183 Prototype feed creation 2021-01-19 23:17:03 -05:00
14d2d19ae1 Tests for Miniflux feed listing 2021-01-17 13:02:31 -05:00
2cf4bf0d4d Prototype Miniflux feed listing 2021-01-16 22:52:07 -05:00
86897af0b3 Add ability to enable scraper
Also transfer any existing scraper booleans on database upgrade. It was
previously possible to enable scraping manually by editing the database,
and these settings will be honoured.
2021-01-16 19:06:20 -05:00
7897585d98 Test scraping
Text search should also match scraped content when appropriate
2021-01-16 17:58:31 -05:00
76f70119fd More work on scraping 2021-01-16 16:48:35 -05:00
4cb23dd198 Partial implementation of proper content scraping 2021-01-16 14:24:01 -05:00
e74b44cc39 Change favicon to icon_url and add icon_id 2021-01-15 23:15:22 -05:00
2536c9fe03 Last tests for article filters 2021-01-15 23:02:33 -05:00
9f2b8d4f83 Imprement setting of filter rules 2021-01-14 12:42:33 -05:00
618fd67f80 Set marks for filtered articles on feed refresh 2021-01-13 14:54:22 -05:00
7a6186f2d7 Update Miniflux documentation 2021-01-13 14:43:29 -05:00
097362881b Tests for filtering during feed parsing 2021-01-11 23:12:43 -05:00
a4146ec129 Start on test for filtering during feed parsing 2021-01-11 09:53:09 -05:00
9e29235d87 Don't fetch from example.com during tests 2021-01-08 16:46:21 -05:00
549c7bdc72 Style fixes 2021-01-08 15:47:19 -05:00
4f34b4ff29 Rule refactoring
- The Database class is now responsible for preparing rules
- Rules are now returned in an array keyed by user
- Empty strings are now passed through during rule preparation
2021-01-08 14:17:46 -05:00
c1eff8479c Simplify configuration property caching 2021-01-07 19:49:09 -05:00
6dba8aa66b Fixes for rules
- Whitespace is now collapsed before evaluating rules
- Feed tests are fixed to retrieve a dumy set of rules
- Rule evaluation during feed parsing also filled out
2021-01-07 15:08:50 -05:00
461e256052 Work around MySQL syntax weirdness
Also improve test for token translation to actually test that the
translated tokens are accepted by the database system
2021-01-07 10:12:38 -05:00
47ae65b9d3 Function to apply filter rules 2021-01-03 22:15:39 -05:00
b12f87e231 Support Xdebug 3.x for coverage 2021-01-03 16:51:25 -05:00
ffc5579a7a Partial implementation of filter rule handling 2021-01-03 16:41:15 -05:00
7e17332714 Implement marking all as read for Miniflux 2020-12-31 17:50:40 -05:00
31f0539dc0 Implement Miniflux user deletion 2020-12-31 17:03:08 -05:00
bf95b134bd Fix up error codes for category changes 2020-12-31 15:46:47 -05:00
197922f92f Implement Miniflux user creation 2020-12-31 13:57:36 -05:00
ee0c3c9449 Tests and fixes for user modification 2020-12-30 17:01:17 -05:00
cc648e1c3a Update tooling 2020-12-28 11:42:36 -05:00
67f577d573 Bump emulated Miniflux version 2020-12-28 08:43:54 -05:00
f58005640a Prototype user modification 2020-12-28 08:12:30 -05:00
2946d950f2 Forbid more user names
- Control characters are now forbidden
- Controls and colons are now also forbidden when renaming
2020-12-27 10:08:00 -05:00
405f3af257 Invalidate sessions and Fever passwords when renaming users 2020-12-25 22:22:37 -05:00
5ec04d33c6 Add backend functionality to rename users 2020-12-25 17:47:36 -05:00
88cf3c6dae Test filter rule retrieval 2020-12-23 09:38:22 -05:00
d66cf32c1f Style fixes 2020-12-22 16:13:12 -05:00
a81760e39d Aggressivly clean up hidden articles
Notably, starred articles are cleaned up if hidden
2020-12-22 15:17:18 -05:00
ade0402210 Adjust TT-RSS to ignore hidden items 2020-12-21 21:49:57 -05:00
f33359f3e3 Move some Miniflux features to abstract handler 2020-12-20 22:30:59 -05:00
b7ce6f5c79 Adjust Fever to ignore hidden items 2020-12-20 19:32:07 -05:00
b2fae336e8 Adjust Nextcloud News to ignore hidden items 2020-12-20 17:42:28 -05:00
f0bfe1fdff Simplify editionLatest Database method
Also adjust label querying to take hidden marks into account
2020-12-20 17:34:32 -05:00
8527c83976 Exclude hiddens from subscription unread count
Also fix a bug that would result in the unread count being null if
no marks existed
2020-12-20 11:55:36 -05:00
97010d8822 Tests for marking articles hidden 2020-12-19 10:59:40 -05:00
86c4a30744 Adjust articleStarred function to discount hidden 2020-12-17 18:12:52 -05:00
ffc98daff3 Adjust article marking tests to account for new hidden mark 2020-12-15 19:50:26 -05:00
8ae3740d5f Implement querying articles by hidden mark 2020-12-15 19:28:51 -05:00
d5cd5b6a17 Implement hidden marks
Tests are still needed
2020-12-15 13:20:03 -05:00
c43d0dcae3 Groundwork for filtering rules 2020-12-14 20:09:38 -05:00
95a2018e75 Implement caategory marking as read 2020-12-14 12:41:09 -05:00
5124f76b70 Implementcategory deletion 2020-12-13 22:10:34 -05:00
eb079166de Tests for category renaming 2020-12-13 12:56:57 -05:00
3ebb46f48e Some work on categories 2020-12-11 23:47:13 -05:00
2e6c5d2ad2 Query Miniflux categories 2020-12-11 13:31:35 -05:00
4b73698381 More user query tests 2020-12-10 23:19:26 -05:00
ebdfad535c More Miniflux user tests
Also added user lookup functionality
2020-12-10 20:08:00 -05:00
7c841b5fc2 Test for listing users 2020-12-09 23:39:29 -05:00
5c83655541 Add modification timestamp to user metadata 2020-12-08 16:10:23 -05:00
d85988f09d Prototype Miniflux user querying 2020-12-08 15:34:31 -05:00
2eedf7d38c Finally fix MySQL 2020-12-07 09:52:42 -05:00
e9d449a8ba Fix user manager and tests 2020-12-07 00:07:10 -05:00
ce68566fcb Hopefully fix MySQL 2020-12-06 20:27:20 -05:00
a431243421 Fixes for MySQL and PostgreSQL 2020-12-06 13:17:19 -05:00
fcf1260dab Adjust database portion of user property manager 2020-12-05 22:13:48 -05:00
978929aabd WIP redesign of user properties 2020-12-05 11:01:44 -05:00
0f3e0411f0 Document some differences frrom Miniflux 2020-12-03 15:15:23 -05:00
94154d4354 Implement Miniflux feed discovery 2020-12-02 18:00:27 -05:00
669e17a1f6 Add ability to discover multiple feeds 2020-12-01 17:12:19 -05:00
2a0d6e6599 OPTIONS tests 2020-12-01 12:08:45 -05:00
7fa5523a7d Simplify handling of invalid paths and methods 2020-12-01 11:06:29 -05:00
def07bb1ad Tests for Miniflux authentication
This appears to match Miniflux's behaviour
2020-11-30 10:52:32 -05:00
8c059773bb Update tooling 2020-11-30 10:51:39 -05:00
90117b5cd7 Fix Miniflux strip value 2020-11-26 08:42:35 -05:00
06dee77bac First tests for Miniflux 2020-11-23 09:31:50 -05:00
f6cd2b87ce Port token data from Microsub branch 2020-11-18 11:25:28 -05:00
d4bcdcdadd Fix TTRSS coverage 2020-11-18 10:01:20 -05:00
d3ebb1bd56 Last set of tests for user management. Fixes #180 2020-11-17 16:23:36 -05:00
e16df90bae Style fixes 2020-11-16 10:26:14 -05:00
180b4ecc9b More user tests 2020-11-16 10:24:06 -05:00
27d9c046d5 More work on user management 2020-11-16 00:11:19 -05:00
7f2117adaa Differentiate between duplicate/missing users and other failure modes 2020-11-15 16:24:26 -05:00
351f972512 Tests for internal user driver 2020-11-13 21:41:27 -05:00
4baf5fa2f9 Tests for new user functionality in Database 2020-11-13 19:30:23 -05:00
dde9d7a28a Refinements to user manager
A greater effort is made to keep the internal database synchronized
2020-11-11 18:50:27 -05:00
eb2fe522bf Last bits of the new user metadata handling 2020-11-10 17:09:59 -05:00
5a17efc7b5 Clean up user driver API
- It is no longer assumed a driver knows whether a user exists
- The $password param is now required (but nullable when setting
2020-11-09 18:14:03 -05:00
771f79323c Strip out remnants of the authorizer 2020-11-09 16:51:30 -05:00
576d7e16a8 Fix handling of bytea-typed nulls 2020-11-09 16:49:42 -05:00
1b1789988a More client compatibility updates 2020-11-09 14:47:42 -05:00
532ce4a502 Prototype changes to user management
The driver itself has not been expnaded; more is probably required to ensure
metadata is kept in sync and users created when the internal database does
not list a user an external database claims to have
2020-11-09 13:43:07 -05:00
ee050e505c Add more Android clients to manual 2020-11-07 14:43:46 -05:00
9fb185a8e2 Add TT-RSS Web client to manual 2020-11-07 12:00:41 -05:00
b62c11a43e Lasts tests for icon cache; fixes #177 2020-11-07 08:11:06 -05:00
1d3725341a Fix detection of Xdebug for coverage 2020-11-06 19:56:32 -05:00
311910795a More tests for icon cache 2020-11-06 17:06:01 -05:00
3d3c20de5c Don't anticipate API features 2020-11-06 15:57:27 -05:00
4d532cba3f Initial Miniflux documentation 2020-11-06 13:08:53 -05:00
e861cca53d Integrate schema change necessary for microsub 2020-11-06 11:06:27 -05:00
b24c469dca Update changelog 2020-11-06 11:01:50 -05:00
8f739cec85 Excluse empty-string URLs from icons table 2020-11-06 10:28:28 -05:00
424b14d2b4 Clean up use of subscriptionFavicon 2020-11-06 10:27:30 -05:00
dd1a80f279 Consolidate subscription icon querying
Users and tests still need adjusting
2020-11-05 18:32:11 -05:00
4fc208d940 More consistent icon API 2020-11-05 16:51:46 -05:00
c3a57ca68b Tests for icon cache population 2020-11-05 14:19:17 -05:00
bd650765e1 Generalize icon fetching tests 2020-11-05 12:12:01 -05:00
50fd127ac4 Test for icon fetching 2020-11-05 10:14:42 -05:00
7c40c81fb3 Add icons to the database upon feed update 2020-11-05 08:13:15 -05:00
c25782f98c Partial icon handling skeleton 2020-11-04 20:00:00 -05:00
af675479b8 Remove excess whitespace 2020-11-04 18:35:36 -05:00
2438f35f3d Add icon cache to database
Feed updating has not yet been adapted to store
icon data (nor their URLs anymore)
2020-11-04 18:34:22 -05:00
5e60da00a9 Merge branch 'master' into miniflux 2020-11-03 18:58:09 -05:00
b5f959aabf Fix blob tests 2020-11-03 18:57:26 -05:00
41bcffd6fb Correctly query PostgreSQL byte arrays
This required different workarouynd for the native and PDO interfaces
2020-11-03 17:52:20 -05:00
c21ae3eca9 Correctly send binary data to PostgreSQL
This finally brings PostgreSQL to parity with SQLite and MySQL.
Two tests casting binary data to text were removed since behaviour here
should in fact be undefined

Accountinf for any encoding when retrieving data will be addressed by
a later commit
2020-11-02 15:21:04 -05:00
c92bb12a11 Prototype Miniflux dispatcher 2020-11-01 19:11:01 -05:00
905f8938e2 Typo 2020-11-01 09:37:59 -05:00
8ad7fc81a8 Initially mapping out of Miniflux API 2020-10-31 21:26:11 -04:00
16d2e01668 New schema for PostgreSQL and MySQL 2020-10-30 19:00:11 -04:00
4db1b95cf4 Add numeric IDs and other Miniflux data to SQLite schema 2020-10-30 15:25:22 -04:00
3ac010d5b6 Fix tests in absence of database extensions 2020-10-30 12:16:03 -04:00
b58a326461 Prepare for schema changes 2020-10-29 11:58:45 -04:00
e9682bc601 Correct typos 2020-10-27 14:39:02 -04:00
5a09dcb3ed Update changelog 2020-10-27 10:58:54 -04:00
e3ebd89aa8 Add NewsFlash to supported clients 2020-10-27 10:54:35 -04:00
0117e7f9bf Relax Fever's HTTP correctness for client compat
- Unread on iOS appears to send all API requests as GETs
- Newsflash on Linux sends multipart/form-data input
2020-10-27 10:49:54 -04:00
7ef02e1d65 Revert wishful thinking 2020-10-27 09:48:47 -04:00
3d909da1e5 Update iOS/macOS client URLs 2020-10-26 16:09:42 -04:00
6520ed38fb Update dependencies 2020-10-26 14:44:54 -04:00
9b11001402 Fix sample Nginx configuration 2020-10-26 12:57:16 -04:00
2e0ed5e923 Add reference to MariaDB missing features 2020-09-24 09:46:55 -04:00
afbf3be031 Use 'union all' instead of 'union' in queries 2020-09-22 11:34:52 -04:00
6ee7ca4aa4 Update PicoFeed to latest release version 2020-09-15 08:45:49 -04:00
9bc1373998 Version bump 2020-09-09 18:24:42 -04:00
0f3ada598e Work around Picofeed bug
A fix is pending merging upstream
2020-09-09 14:52:35 -04:00
d29917caf8 Update Yarn again 2020-09-09 14:33:14 -04:00
e75d15e552 Update dependencies 2020-09-09 10:59:28 -04:00
77aef9c995 Yarn update 2020-07-19 09:08:43 -04:00
ee549cdee7 Appease GitHub (yet again) 2020-06-22 11:32:17 -04:00
2620314583 GitHub should now be kept in sync at all times 2020-04-25 11:49:45 -04:00
20fe65b67e Make changelog point to manual instead of readme 2020-04-25 11:43:36 -04:00
66a4f71ef3 Minor simplification 2020-04-25 11:42:23 -04:00
5f35a680e3 Appease GitHub (again) 2020-03-31 12:53:11 -04:00
c1e27684d4 Appease GitHub 2020-03-20 16:51:47 -04:00
1268e5fd73 Finally fix DST error in tests 2020-03-11 16:21:42 -04:00
7777ff962f Group more style rules under PSR-12 2020-03-01 21:07:36 -05:00
e60f7ea03f Add class constant visibility 2020-03-01 18:32:01 -05:00
bc53a2d24a Style fixes 2020-03-01 15:16:50 -05:00
a7f69c845f Use a more specific house style 2020-03-01 15:15:57 -05:00
3aa17ee70f Whitespace 2020-03-01 10:17:16 -05:00
e8091fa740 Speed up coverage slightly. 2020-02-24 18:52:14 -05:00
9696d55b31 Prepare ugfix release 2020-02-16 17:43:46 -05:00
670fb61299 Merge branch 'php7.1' 2020-02-16 17:37:38 -05:00
1d514e4739 Remove forked picofeed repository 2020-02-16 17:37:13 -05:00
cf4a9e6436 Use archived copy of MySQL critique 2020-02-03 16:55:45 -05:00
39a1895867 Refine and cover new Guzzle error handling 2020-02-03 16:54:45 -05:00
cb41912f36 Merge branch 'master' into php7.1 2020-02-02 15:45:58 -05:00
d7e10e40ee Prefer PCOV for code coverage 2020-02-01 23:43:46 -05:00
04878bda9d Documentation changes 2020-01-25 11:18:51 -05:00
06d3af0ac3 Fix remaining Picofeed-related test failures 2020-01-24 15:54:08 -05:00
9cb7cf485d Style fix 2020-01-23 17:07:58 -05:00
e29c573210 Update tools 2020-01-23 17:07:32 -05:00
49d003082d Fix problems with nicolus/picofeed
This involved multiple fixes to Picofeed itself, not all of which have
been merged upstream yet
2020-01-23 17:07:20 -05:00
e583ffea67 Start on integrating well-maintained picofeed fork
Picofeed configuration does not seem to get passed to Guzzle.
2020-01-21 08:42:38 -05:00
b5f118e8cb Cleanup 2020-01-20 13:52:48 -05:00
bbace7a0ac Use void and nullable return types where practical 2020-01-20 13:34:03 -05:00
5838af892f Replace references to Zend with Laminas 2020-01-20 10:40:05 -05:00
0565553d4a Change PHP requirement in manual 2020-01-20 00:38:38 -05:00
9e1087914c Change tool dependencies to "dev" type 2020-01-20 00:36:42 -05:00
2fa2799b64 Require PHP 7.1 in Composer
In reality we have unwittingly required PHP 7.1 for some time
2020-01-20 00:34:02 -05:00
14ef33879b Re-arrange database tests by engine 2020-01-19 18:13:32 -05:00
c4ee7254cd Refactor some tests to use data providers 2020-01-08 12:02:43 -05:00
fb2602fe04 Don't load actual configuration during CLI tests 2019-12-27 09:00:22 -05:00
38501ce4f2 Add Newsie to documented clients 2019-12-25 21:35:34 -05:00
cca56b09fc Order failing tests first
This only seems to work under some conditions, but it's a start.
2019-12-07 12:03:41 -05:00
4421a9e510 Offload coveraage whitelisting to xdebug 2019-12-07 10:26:48 -05:00
524b1ca140 Merge branch 'master' into phpunit-8 2019-12-06 18:26:28 -05:00
3ee89bf669 Version bump 2019-12-06 17:46:53 -05:00
c9d4540616 Merge branch 'mysql' 2019-12-06 17:44:29 -05:00
3cb8dfafe2 Change download URL in manual 2019-12-06 17:43:50 -05:00
bbe70b6abc Corect CamelCase references to Nextcloud 2019-12-05 13:02:02 -05:00
794fb506a5 Widen URL field 2019-12-03 22:33:44 -05:00
484510cf8c Expand text fields other than user id and feed URL 2019-12-03 17:10:47 -05:00
e3144ecbf5 Update tool dependencies 2019-12-02 19:12:48 -05:00
5df89009e6 Merge branch 'master' of https://code.mensbeam.com/MensBeam/arsse 2019-12-02 19:09:16 -05:00
15de8c2320 Fix PostgreSQL connection error message 2019-12-02 17:14:03 -05:00
f4b08170bf Fix MySQL schema 2019-12-02 16:38:41 -05:00
568e6e4660 Delete dangling MySQL records when updating
MySQL seems to reject queries involving arsse_folders.parent and
arsse_subscription.folder, though they appear to be valid. More testing
is required.
2019-12-01 22:29:48 -05:00
737dd9f6b8 Fix foreign keys in MySQL 2019-12-01 15:00:28 -05:00
c1a3e64bfa Fix new tests to work with PHPUnit 8 2019-11-14 12:05:10 -05:00
de424e42f6 Merge branch 'master' into phpunit-8 2019-11-14 11:51:38 -05:00
c6b79d49ba Documentation corrections 2019-10-28 19:48:10 -04:00
5ede4cbdb2 Clarify coverage requirements 2019-10-28 13:14:31 -04:00
bbc96e4f37 Remove reference to obsolete CSS-only manual task 2019-10-28 13:09:10 -04:00
733f0d7fd5 Fix typo 2019-10-28 12:43:47 -04:00
0e5b242c67 Version bump 2019-10-28 11:07:04 -04:00
c59cdfef76 Whitespace fixes 2019-10-25 15:16:35 -04:00
71c7cd8fb1 Full coverage! Fixes #66 2019-10-19 18:51:01 -04:00
728eecfbb5 Additional service tests 2019-10-19 12:14:13 -04:00
bad86cedb3 Tests for bootstrapper 2019-10-19 12:13:42 -04:00
7ac4fb4715 Clarify PDO workaround for SQLite 2019-10-18 16:09:01 -04:00
3ef1177f06 Remove driver lists 2019-10-18 13:20:28 -04:00
b6dd8ab20d Improvements to and proper tests for query builder 2019-10-18 13:11:03 -04:00
c706a76057 Simplify array flattening 2019-10-18 13:10:03 -04:00
c3643fba10 Tests for URL::absolute() 2019-10-17 16:23:41 -04:00
64c3ec3571 Coverage fixes and OPML bugfix 2019-10-17 13:00:56 -04:00
17a2fa96f8 Upgrade to PHPUnit 8 2019-10-16 14:42:43 -04:00
b8b8a6aa70 Also adjust test for MySQL connection failure 2019-10-16 10:26:39 -04:00
cb1039326c Fix PostgreSQL tests when using "trust" authentication 2019-10-11 13:04:15 -04:00
cf5c08459f Remove unused Daux libs 2019-10-11 12:51:32 -04:00
5d70f9fc11 Update dependencies 2019-10-11 12:02:22 -04:00
9461cefc35 Upgrade Daux to 0.11 2019-10-11 12:00:48 -04:00
1809fb254e Deal with trailing whitespace in media types 2019-09-27 22:54:33 -04:00
4f5a8e3180 Make media type checking more robust 2019-09-27 22:38:03 -04:00
5f993187ea Be explicit with HTTP challenge character encoding 2019-09-27 17:16:34 -04:00
c5337b37b4 Consolidate creation of synthetic server requests 2019-09-25 18:30:53 -04:00
67bde97e0c Update changelog 2019-09-12 11:50:33 -04:00
a143c86136 Set up test better 2019-09-12 11:14:30 -04:00
3da884dfbc Don't embed ito SQL strings with question marks
Fixes #175
2019-09-12 09:53:43 -04:00
be5ad50f54 Tests for text search clause generator 2019-09-12 09:41:01 -04:00
fb6e2babb9 Change some conditions to asserts 2019-09-12 08:32:40 -04:00
d0f780d4e6 Unit tests for IN() clause generator 2019-09-11 15:25:26 -04:00
3aac583c00 Add Microsub to possible future APIs 2019-09-05 14:02:06 -04:00
5620070106 Update changelog 2019-09-05 13:59:19 -04:00
b0517ddda1 Merge branch 'urlnorm' 2019-09-05 13:58:14 -04:00
c9e86e71c4 More style fixes 2019-09-05 11:25:50 -04:00
53aa7a4d0d Style fixes 2019-09-05 10:21:36 -04:00
d9c769d40e Remove Target class 2019-09-05 10:19:05 -04:00
6235cb0be6 Fix errors in last commit 2019-09-05 10:13:17 -04:00
29667464a1 Remove unnecessary namespace imports 2019-09-05 10:03:32 -04:00
0eb0fbcc0d Also normalize relative URLs 2019-09-03 19:34:56 -04:00
3439895779 Normalize URLs before establishing feed uniqueness 2019-09-03 19:04:23 -04:00
a175561574 Documentation corrections 2019-09-03 13:26:00 -04:00
bd71ddb929 Percent-encoding and IPv6 normalization 2019-09-03 13:16:05 -04:00
03262e7f44 Typo fix 2019-08-29 19:06:59 -04:00
f159965b56 Add more cautious about using MySQL 2019-08-29 13:53:32 -04:00
3f3a571fc1 Go back to mainline Daux 2019-08-29 13:06:38 -04:00
12fe786a2f Cleanup 2019-08-29 12:28:23 -04:00
d4802bcdb6 Handle IDNs
While IPv6 address normalization was originally planned, this was deemed
too much effort to bother with such a niche feature; IPv6 addresses are
instead passed through unmodified
2019-08-27 15:18:02 -04:00
dc750acf07 Handle ports, paths, and credentials correctly 2019-08-27 11:08:13 -04:00
13c27c2536 Start on URL normalizer 2019-08-26 22:13:30 -04:00
f688155ca4 Note manual in changelog 2019-08-25 13:28:26 -04:00
e5696fdf44 Merge branch 'manual' 2019-08-25 13:21:44 -04:00
bab4174a17 Move database driver helps out of test cases 2019-08-25 13:19:11 -04:00
fa3cfcd589 Correct error in stand-alone Nginx sample 2019-08-24 15:23:52 -04:00
5bf791709b Add an index for the "Getting Started" section; other tweaks 2019-08-24 15:02:29 -04:00
3ea2eeb817 Clarify the list of requirements is informational 2019-08-24 10:03:35 -04:00
d5af499a6e Typo 2019-08-22 19:41:46 -04:00
1ce54372b7 Add a mission statement of sorts 2019-08-22 19:07:57 -04:00
e122f97036 Standardize section capitalization
This does not (yet) apply to files, but may later
2019-08-22 14:16:26 -04:00
d7f1963d7f Move untested clients to an untested section 2019-08-22 14:15:45 -04:00
2c1dcdcd6d Update Daux theme using custom changes for now 2019-08-22 13:30:10 -04:00
a24bfa0683 Code blocks are now properly themed 2019-08-17 12:57:43 -05:00
f102992d20 Typos 2019-08-16 09:02:11 -04:00
a563d174d5 Tooling documentation 2019-08-15 22:35:02 -04:00
3ff93113fa Documentation on repo structure 2019-08-15 19:39:08 -04:00
934c762cb8 Avoid problematic use of realpath 2019-08-14 12:21:08 -04:00
06b9049515 Update Daux and manual theme 2019-08-13 07:01:17 -04:00
ca49785f6f Anticipate the next release of Daux 2019-08-11 12:48:18 -04:00
2a19777537 Update Daux 2019-08-09 07:54:17 -04:00
294f3648a2 Fix JS bug in Daux
See https://github.com/dauxio/daux.io/pull/122 for patch to upstream
2019-08-08 18:34:58 -04:00
440ea11e27 Clarify highlight.js download process 2019-08-08 18:07:18 -04:00
705e506bfc Manual theme design iteration 2019-08-08 14:34:55 -05:00
edf92b603b Preliminary work on manual theme design 2019-08-07 13:37:40 -05:00
16530b9a66 Probably test for feed redirection bug 2019-08-06 14:17:56 -04:00
cfef75ccba Don't update the feed URL when fetching
This can result in some unpredictable constraint violations
2019-08-06 09:58:30 -04:00
265f3a1b6d Correct error in Nginx example 2019-08-06 08:31:20 -04:00
262980d564 Process color() functions in stylesheet 2019-08-05 17:46:00 -04:00
b8b559b0f8 Correct spuriously failing tests 2019-08-05 16:33:48 -04:00
150e301378 Fix manual cleanup 2019-08-04 21:59:25 -04:00
ba05d53626 Rededicate the README file as a programmer's guide 2019-08-04 21:00:21 -04:00
987ee631ab Add manual to packaging
Excludes JS-related files from archive

Also default to "HEAD" instead of "head": Unix is case-sensitive
2019-08-04 20:49:50 -04:00
f360c64327 Slight fixes to Robo and PHPUnit 2019-08-04 19:21:09 -04:00
246263fa04 Fill out general info 2019-08-04 16:55:08 -04:00
e7e4c823e4 Clean up theme files after generation 2019-08-04 16:21:58 -04:00
c334390db1 Robo task for rebuilding stylesheet only 2019-08-04 11:02:33 -04:00
6f8182b940 Working theme builder 2019-08-04 10:57:05 -04:00
b3566f8d48 Use Robo to extract zip archive 2019-08-03 22:06:57 -04:00
b514ac983b Rely on Zip extension for extracting highlight.js 2019-08-03 21:32:13 -04:00
7dee5498d8 Procedure for downloading highlight.js
Currently slightly broken due to a bug in a dependency
2019-08-02 22:34:41 -04:00
bacce0461a Fix JS dependency graph 2019-08-02 13:11:22 -04:00
8bbcae4aa4 First attempt at a custom Daux theme 2019-08-02 11:15:48 -04:00
c935091d99 Fix bad link 2019-08-01 21:27:08 -04:00
3507a74ab0 Upgrade notes 2019-07-31 19:16:27 -04:00
67cdf52d3a Typo 2019-07-31 17:43:34 -04:00
899f28dda5 Documentation for service and cron 2019-07-31 17:42:37 -04:00
1ef669a464 Documentation for user management and OPML 2019-07-31 14:57:43 -04:00
daa3be90c6 Grammar fix 2019-07-31 09:40:47 -04:00
91b9bf808d Correct outdated requirements 2019-07-31 09:31:55 -04:00
7f9f11bbf4 Consolidate configuration samples 2019-07-31 09:12:12 -04:00
e76871c567 Changelog so far 2019-07-31 08:02:09 -04:00
d9d9394c97 Information on the configuration file 2019-07-30 13:29:54 -04:00
5402a1688b Configuration sample for Apache 2019-07-30 12:34:46 -04:00
87cd879d8f More Fever details 2019-07-29 22:59:45 -04:00
580342006e Sample configuration for Nginx 2019-07-29 22:49:36 -04:00
6705ea1585 Re-organize sections 2019-07-29 14:58:00 -04:00
4fd060f001 Various documentation tweaks
- Place protocol infoboxes above intro text
- Fill out Fever text from README
- Fix MySQL version
- Add note that Percona Server should work
2019-07-28 14:36:06 -04:00
7d82725a03 Add Liferea to clients list 2019-07-28 11:33:33 -04:00
a7ae38589b Database documentation corrections 2019-07-28 07:58:01 -04:00
05fe34fec2 Sdd note about cron 2019-07-28 07:46:00 -04:00
4837aa2d3d Minor enhancements to instructions 2019-07-28 07:39:47 -04:00
5897562685 Installation instructions 2019-07-28 07:26:16 -04:00
f2b456d709 Start on installation page 2019-07-27 19:01:49 -04:00
96342eccb1 Tweaks 2019-07-27 18:40:41 -04:00
6bb382e7c4 Documentation for databases 2019-07-27 17:37:00 -04:00
f0c99edd22 Various corrections 2019-07-27 17:36:41 -04:00
8a184ae99d Move inline style rules out to a stylesheet 2019-07-27 12:15:19 -04:00
eb120b4629 Fetch in three hours if caching is via etag 2019-07-27 11:03:17 -04:00
137be8bde2 Fix whitespace 2019-07-26 23:24:29 -04:00
45a43488ee Increase compatibility with misbehaving Fever clients 2019-07-26 23:23:22 -04:00
db5bcb78a3 Fix lookup of evergreen tokens 2019-07-26 20:06:47 -04:00
85fa1e48ab Add Fever to list of protocols 2019-07-26 19:43:52 -04:00
f50a78eafa Add basic Fever data; expand client list
Most new clients yet to be tested, but Newsout works fine.
2019-07-26 19:06:02 -04:00
d6220c1bbb Merge branch 'master' into manual 2019-07-26 11:27:14 -04:00
77b719660b Date 0.8.0 release 2019-07-26 09:43:45 -04:00
9f7e1c915c Start after PostgreSQL and MySQL when relevant 2019-07-26 09:42:36 -04:00
4282ba1c26 Version bump 2019-07-26 09:39:46 -04:00
f7240301e4 Basic database maintenance
Closes #169
2019-07-26 09:37:51 -04:00
cef31907d3 Cron functionality is not new 2019-07-25 22:39:54 -04:00
422eaf9605 Invalidate sessions on password change; closes #170 2019-07-25 22:34:58 -04:00
be92d2f052 Documentation update; fixes #168 2019-07-25 19:23:35 -04:00
faf524c54f CLI test for import
Fixes #35
2019-07-25 15:45:18 -04:00
13b76dea0c Tests for generic importing 2019-07-25 13:14:29 -04:00
0e95892aea Do not necessarily ignore blank tags in import
We still make them practically impossible in OPML imports, however
2019-07-24 14:20:17 -04:00
2aa16f3405 Merge branch 'fever' into opml 2019-07-24 14:04:04 -04:00
56bb460820 Test answering OPTIONS requests in Fever 2019-07-24 12:32:00 -04:00
61b942df70 Defer Fever favicons to a future release 2019-07-24 12:27:50 -04:00
0480465e7e Test Fever XML responses
Fixes #158
2019-07-24 09:10:13 -04:00
8f9678b8a4 Tests for baasic import errors 2019-07-05 21:18:30 -04:00
103755cfb4 Test fixture for import tests 2019-07-05 19:01:34 -04:00
30cede9ea4 Make OPML parser protected 2019-07-05 14:58:05 -04:00
61fe673e20 Skeleton for import tests 2019-06-23 18:45:24 -04:00
cb71a9efd7 Make database connections for testing configurable 2019-06-22 10:29:26 -04:00
2628ff7bf4 Make database test helpers generic 2019-06-21 18:52:27 -04:00
12ef3e649f Mock AbstractImportExport directly 2019-06-21 13:55:49 -04:00
7046ce163c More format-neutral code out of OPML class 2019-06-21 13:47:34 -04:00
92b1626dba Remove most unused features of the query builder
Experience has proved programmatically setting joins is not useful, and
getting the types and values of query parts was not being maintained.

The programmatic setting of GROUP BY may be useful in future, however.
2019-06-21 12:00:23 -04:00
62fe3a7298 Fix case of vfsstream tool dependency 2019-06-21 10:30:36 -04:00
54aaab50b5 Update tools 2019-06-20 15:57:49 -04:00
c1e13e6199 Tests for file imports 2019-05-12 16:33:19 -04:00
be5a1fb94f Mixed content test for OPML 2019-05-08 20:24:16 -04:00
0f7d49c21e More OPML tests and fixes 2019-05-06 19:36:39 -04:00
644750487c Command line documentation and fixes 2019-05-06 00:02:59 -04:00
a30114807f Tests and fixed for OPML feed parsing 2019-05-05 20:29:44 -04:00
cdd9f4dfbe More OPML parser tests 2019-05-02 21:54:49 -04:00
5ba009cfed First set of OPML parser tests 2019-05-02 12:52:52 -04:00
6ef13d0880 Style fixes 2019-05-01 22:52:20 -04:00
b9821d925a CLI for OPML import, and proper exceptions 2019-05-01 10:46:44 -04:00
67492cd7ef Prototype OPML importer routine
In theory the import (as opposed to parse) routine could be used for any
format; this could be used to implement an ad hoc JSON format to avoid
the loss of commas in tags with OPML
2019-04-27 19:50:03 -04:00
3899ee6b4e Allow for replacing label and tag associations
This supplements adding and removing
2019-04-27 18:32:15 -04:00
2af223753d Function to add a feed without a subscription 2019-04-21 14:07:36 -04:00
ceecd58393 OPML parsing comments and minr fixes 2019-04-21 13:10:47 -04:00
825c286e5b Prototype OPML import parser 2019-04-19 18:01:31 -04:00
2d18be959c Tests for undoing read marks 2019-04-10 18:27:57 -04:00
daeff63239 Test basic Fever responses 2019-04-10 16:01:58 -04:00
c55a960b85 Slight cleanup 2019-04-10 15:14:45 -04:00
efd8492573 Tests for various invalid requests 2019-04-10 15:07:34 -04:00
8532c581a8 Handle OPTIONS requests in Fever 2019-04-10 10:51:02 -04:00
afb95e53b0 Initial implementation of read-undo 2019-04-10 10:21:14 -04:00
52bc5fbda6 Tests for simple marking 2019-04-10 09:48:28 -04:00
be4f3b0657 Merge branch 'master' into fever 2019-04-09 16:31:58 -04:00
61abf7ee7c Upgrade to Diactoros 2.x 2019-04-09 16:15:36 -04:00
15915a4393 Initial implementation of simple marks 2019-04-08 23:31:22 -04:00
c783ec4357 Prototype XML output for Fever 2019-04-08 20:58:45 -04:00
98fc3f4940 Test for hot links 2019-04-08 19:21:21 -04:00
e8f4732b1f Tests for saved and unread item ID lists 2019-04-08 19:15:12 -04:00
4ce371ece6 Tests and fixes for Fever item listing 2019-04-08 18:41:56 -04:00
e3d2215920 Style fixes 2019-04-05 11:03:15 -04:00
0ef606aa03 Return string list of item IDs 2019-04-05 08:20:05 -04:00
0752e9cf3d Implement Fever sync 2019-04-04 19:37:48 -04:00
982f09c9aa Upgrade notes 2019-04-04 18:05:26 -04:00
7c85e837df Documentation update 2019-04-04 18:01:57 -04:00
c6d241e653 Implement Fever item list 2019-04-04 17:57:12 -04:00
12f23ddc16 Updated tests for arbitrary sorting 2019-04-04 17:21:23 -04:00
f72c85c9f6 Hopefully working but maybe broken custom sorting 2019-04-04 11:22:50 -04:00
156ce2d099 Fix Unix Robo script 2019-04-04 11:20:40 -04:00
4b133bddd6 Prototype arbitrary result ordering 2019-04-03 15:02:59 -04:00
74fc39fca0 Implement multi-folder context option 2019-04-02 22:44:09 -04:00
cce1089e10 Handle edge case with folder 0
Folder 0 (the root folder) is a valid, though nonsensical selection:
using it as a positive option is the same as not using the option at
all, and using it as a negative option necessarily yields an empty set.

However, it can in some contexts be validly specified, and so it should
be handled consistently. It had not been previously, but is now.
2019-04-02 19:58:35 -04:00
98f6fca7e3 Enforce minimum array size (for now) 2019-04-02 18:37:46 -04:00
ef1b761f95 Implement most multiple-item context options
Selecting multiple folder trees will require further effort
2019-04-02 18:24:20 -04:00
ba32ad2f17 Add context options for multiple tags, labels, etc 2019-04-02 09:32:31 -04:00
77efaa7b41 CLI command for exporting OPML and sundry cleanup 2019-04-01 17:24:19 -04:00
deea294f8a Add export-to-file wrapper for OPML 2019-04-01 16:54:14 -04:00
35e79d53a9 OPML export fixes, with tests 2019-03-30 10:01:12 -04:00
17fd909335 Add DOM extension as a direct dependency
Previously it was already a dependency of PicoFeed,
so there's effectively no change
2019-03-29 10:15:30 -04:00
d63edf541f Insert folders into OPML before subscriptions 2019-03-29 09:02:39 -04:00
25b7b47e0a Prototype OPML exporter 2019-03-28 21:53:04 -04:00
5d994f3dad Normalize Fever input consistently
Two parameters are undocumented, but other implementations consistently
accept them from clients
2019-03-28 14:54:31 -04:00
de615c671a Tests and fixed for Fever feeds and groups 2019-03-27 15:09:04 -04:00
7faec3b0db Fever fixes
- Ensure the last refresh time is included in authenticated requests
- Use a partial mock in auth tests so that other processing does not
get in the way of results
- Make sure the group list includes unused groups
- Make sure the update time of subscriptions is correct
2019-03-27 11:54:47 -04:00
d8407330a0 Add a function to get when feeds were last updated
This is an optimization for Fever, which returns this information with
every API call.
2019-03-26 16:51:44 -04:00
acb3973149 Prototype implementation of Fever groups and feeds 2019-03-26 08:53:26 -04:00
9c61f967e3 Correct CLI password clearing 2019-03-25 17:07:28 -04:00
54be5997d1 CLI tests for password changing and clearing 2019-03-25 15:03:41 -04:00
a7fe879174 Fix CLI auth test 2019-03-25 14:24:58 -04:00
bf3bf9589f Merge branch 'master' into fever 2019-03-25 14:12:08 -04:00
1e83350dd0 Version bump 2019-03-25 11:57:31 -04:00
65f723c7d4 Fix missing reference to author in TT-RSS. 2019-03-25 11:30:35 -04:00
8020457820 Update dependencies 2019-03-25 11:28:15 -04:00
b8640d73f9 Update PHPUnit 2019-03-25 10:47:06 -04:00
22c2629078 Partial tests for new CLI features 2019-03-25 10:45:05 -04:00
f4d4feb69c Suppress TLS error from mock server 2019-03-25 09:53:06 -04:00
7d95e8fc09 Split Fever user management from protocol handler 2019-03-25 08:31:49 -04:00
1ce95ef4d9 Add means of testing Fever authentication 2019-03-24 15:05:21 -04:00
e45ba3f0ea Add means of unsetting a password in the backend 2019-03-24 14:42:23 -04:00
5bf0b67ec3 Increase file descriptor limit for Robo on Linux 2019-03-24 14:41:17 -04:00
94314f3e6d Fix test errors when PostgreSQL or MySQL are not available 2019-03-21 15:51:26 -04:00
fe008d4343 A few more Fever authentication tests 2019-03-21 13:49:55 -04:00
3b28634447 Verify even in exceptional cases 2019-03-21 11:00:07 -04:00
07122b524a Rename Fever user functions for consistency 2019-03-21 10:19:30 -04:00
5480b59d93 Unix Robo fixes 2019-03-20 22:26:50 -04:00
f51d20a863 Unix Robo fixes 2019-03-20 22:25:00 -04:00
9ebaa20633 Tests for Fever password creation and removal 2019-03-20 22:24:35 -04:00
9168155244 Add method to unset a Fever password 2019-03-20 10:42:04 -04:00
1e2d595992 Full set of authentication tests for Fever 2019-03-19 23:37:08 -04:00
d59223bbcb First authentication test for Fever 2019-03-18 22:49:47 -04:00
c32fdf59cd Add details on HTTP authentication in client list 2019-03-16 18:30:00 -04:00
86d52c8ff9 Fix test errors when PostgreSQL or MySQL are not available 2019-03-16 17:48:48 -04:00
b02c910b1e Make token creation check that the user exists 2019-03-10 15:54:43 -04:00
3aa2b62d02 Basic Fever skeleton
Authentication should work, but not tests have been written yet
2019-03-09 22:44:59 -05:00
38bdde1167 Add access tokens to the db, with relevant code
Tokens are similar to sessions in that they stand in for users, but the
protocol handlers will manage them; Fever login hashes are the
originating use case for them. These must never expire, for example,
and we need to specify their values.

This commit also performs a bit of database clean-up
2019-03-09 16:23:56 -05:00
5de1844f6d Add article selection by tag 2019-03-07 11:07:22 -05:00
e6f70527cf Simplify tag summary 2019-03-07 08:20:09 -05:00
ff0c9a3a55 Add functionality for interacting with subscription tags 2019-03-06 22:15:41 -05:00
e2cba68c1b Clarify various SQL queries 2019-03-05 19:25:46 -05:00
4945f8baa3 Clarify various SQL queries 2019-03-05 19:22:01 -05:00
6000d80b7b Work around various SQLite-related problems
- WAL mode was not getting set properly
- Queries using the PDO driver could fail because PDO sucks
2019-03-04 11:08:56 -05:00
ed22090e49 Work around various SQLite-related problems
- WAL mode was not getting set properly
- Queries using the PDO driver could fail because PDO sucks
2019-03-04 11:05:46 -05:00
fb1bdbfb37 Database schema for subscription tags 2019-03-03 12:10:18 -05:00
5efef2c2d0 Console command to refresh all feeds once; fixes #147 2019-03-02 14:59:44 -05:00
44366f48bf Remove arbitrary search term limits; fixes #150 2019-03-02 13:53:43 -05:00
21fdd66d37 Work around limit to SQL parameter placeholders for IN() clauses
Improves #150

LIKE-based matches also need to be similarly conservative
2019-03-01 22:36:25 -05:00
9ef4b4fdfb Update TT-RSS documentation 2019-03-01 12:32:02 -05:00
6857e8ec1b Merge branch 'search' 2019-03-01 12:26:08 -05:00
837f3c6dd6 Simplify SQL type handling
This is done in anticipation of dealing with SQL types in
places other than statements
2019-03-01 12:17:33 -05:00
3b8461b1ca Add searching to TTRSS handler 2019-02-28 16:22:04 -05:00
85307bc90a Add parser for TTRSS search strings 2019-02-28 15:31:33 -05:00
95de375e0b Handle folder and label exclusion
Consequently the way label data are retrieved was completely overhauled
2019-02-27 10:48:11 -05:00
1e7724ec80 Filter out duplicates in set context options 2019-02-26 12:54:27 -05:00
677e33e518 Add text search exclusions 2019-02-26 11:39:19 -05:00
89f25d7b91 Fix coverage a little 2019-02-26 11:12:40 -05:00
0dc82f64d5 Allow ranges in exclusion contexts 2019-02-26 11:11:42 -05:00
70443a5264 Make parent re-association on context clone more restrictive 2019-02-25 23:59:48 -05:00
18d52ea402 Make exclusion contexts return their parent on change 2019-02-25 23:37:14 -05:00
b950ac066f Restrict options in not-context and hopefully make it easier to use 2019-02-25 22:41:12 -05:00
14c02d56ac Implement new context options other than not().
Context handling has also been re-organized to simplify later
implementation of the not() option
2019-02-25 16:26:38 -05:00
f4a74eec5d Add all the other context options allowed by the TTRSS search syntax 2019-02-25 10:46:43 -05:00
2df7c25b66 Add ability to search note text 2019-02-23 20:14:52 -05:00
bc3182a961 Basic substring searching 2019-02-22 18:50:39 -05:00
570a9b171c Revert fulltext detection in driver 2019-02-22 18:49:57 -05:00
ace94e3ef8 Fix context, and context tests 2019-02-22 12:34:06 -05:00
f9fde23708 Context changes to support basic text searching 2019-02-22 11:13:42 -05:00
ad8057a40b Driver changes to support basic text searching 2019-02-22 11:13:13 -05:00
908e1fa310 API documentation for database driver interface 2019-02-21 15:43:19 -05:00
b55d0b374f API documentation for database driver interface 2019-02-21 15:10:32 -05:00
75cba3ca10 Fix fragment identifiers in manual internal links 2019-02-20 10:02:59 -05:00
4316c700a8 Nginx should send the normalized URL to the application 2019-02-19 08:46:17 -05:00
a467115d59 Document TTRSS authentication modes better
The different modes can be likened to either multi-user or single-user
mode in original
2019-02-18 14:40:08 -05:00
17f3a2f059 Start on an API overview for the Database class 2019-02-13 12:37:41 -05:00
5885e14566 Add more compatible clients 2019-02-08 13:08:55 -05:00
b0d5458367 Clarify some prospective protocols 2019-02-04 13:18:33 -05:00
49cefaf5c8 Complete API documentation for the Database class 2019-02-04 13:05:48 -05:00
d3a385beef Partial API documentation for the Database class 2019-02-03 12:25:07 -05:00
ee8701320d Added clients to documentation 2019-01-27 13:31:49 -06:00
b1282b6f6a Upgrade to PHP 7.1 and PHPUnit 7. 2019-01-25 22:07:37 -05:00
e07253867c Add served manual to Robo tasks 2019-01-25 16:56:05 -05:00
6ce1f68ed7 Document supported protocols
Also standardize some stylistic conventions
2019-01-25 16:32:47 -05:00
94ba58fa60 Use latest version of Daux 2019-01-25 15:24:52 -05:00
f5c5d49d97 Document SQL timeout changes 2019-01-24 11:40:25 -05:00
41daf4d176 Merge branch 'master' into manual 2019-01-23 16:46:20 -05:00
500851f161 Style fixes 2019-01-23 16:34:54 -05:00
91b6fdc696 Update changelog; bump version 2019-01-23 16:32:48 -05:00
8ea1df920a Unify SQL timeouts
- Exec and lock timeouts now apply to MySQL
- Lock timeout now applies to PostgreSQL
- SQLite now uses a generic lock timeout setting which applies to all
2019-01-23 16:31:54 -05:00
bc8d443d84 Change PicoFeed dependency to a maintained variant 2019-01-23 12:36:43 -05:00
970731073d Fetch timeout should be a float, not an integer 2019-01-23 09:37:41 -05:00
9120d3b3e3 Correctly escape shell command in subprocesds service driver 2019-01-23 09:32:44 -05:00
a5049ac646 Remove reference to PicoFeed in the User-Agent string
PicoFeed is dead, so there's no point.
2019-01-23 09:21:35 -05:00
37131d3775 Remove non-functional curl service driver for now
Its requiring extensive configuration to function makes me disinclined
to revive it, though it may nevertheless happen.
2019-01-23 09:19:26 -05:00
00ca726e12 Partial first draft of a manual, generated using Daux 2019-01-22 17:49:14 -05:00
4191f77094 Add daux as a dev dependency 2019-01-21 12:15:25 -05:00
05aadfe7c7 Use correct SQLite chema change procedure; version bump 2019-01-21 10:40:39 -05:00
37025bb49f Documentation update 2019-01-21 10:23:25 -05:00
5335d331f7 Fix configuration exporting 2019-01-21 09:55:25 -05:00
6cd81e5656 Temporary list of macOS and iOS clients 2019-01-20 22:44:17 -05:00
5cd84c4ab4 Validate configuration parameters on import, and other changes
- Each parameter is checked for type and normalized
- Interval strings are converted to DateInterval objects
- Timeouts can be specified as interval strings
- Most intervals can be null to signify infinity
- Driver classes are checked that they implement the correct interface
- Short driver names may be used, and are used by default
- Helpful errors messages are printed in case of erroneous configuration

Exporting is currently broken; this will be fixed in an upcoming commit
2019-01-20 22:40:49 -05:00
b0643de21c Add handling of DateInterval objects to ValueInfo 2019-01-17 16:29:42 -05:00
4670dfc849 Handle connection errors 2019-01-15 10:51:55 -05:00
e92bda5373 Various changes:
- Fix handling of binary data and long strings
- Simplify handling of socket connections
- Fix coverage
2019-01-15 08:58:11 -05:00
6ad3fb78a0 Documentation update 2019-01-14 10:46:46 -05:00
f3b0c791f8 Fix remaining tests 2019-01-14 09:51:00 -05:00
5d61ab0a57 Fixes for MySQL native interface
Three test failures remain, but these are minor and will be resolved
soon. Handling of binary data is also broken, but given that this works
fine with the PDO driver, there is presumably some correct method.
2019-01-13 23:17:19 -05:00
e501fbdc87 Remove the DatabaseInformation class in tests and use traits instead 2019-01-12 12:43:06 -05:00
81acba90dc Use strict equality when comparing strings 2019-01-11 10:38:06 -05:00
c4a41255b0 Experimental native MySQL driver
No testing has been performed yet, but changes are extensive enough to
warrant a commit. Of particular note:

- SQL states are enumerated in a separate trait to reduce duplication
- PDOStatement is now an abstract class to avoid duplication of
engine-specific error handling
- Error handling has been cleaned up somewhat
2019-01-10 19:01:32 -05:00
3da773eef6 Grammar tweak 2018-12-22 09:58:56 -05:00
cefc9e5b4d Don't use Canada mirror in links to PHP manual 2018-12-21 18:02:03 -05:00
7191dd5778 Documentation update 2018-12-21 18:00:35 -05:00
206cca35a9 Test tweaks 2018-12-21 17:51:49 -05:00
5a133b795a Correctly munge MySQL queries 2018-12-21 17:37:22 -05:00
b4de56a64e Work around MySQL silliness with same-table foreign keys 2018-12-21 17:02:34 -05:00
f0d30c2eee Make munging of queries a generic feature 2018-12-21 12:35:10 -05:00
24df564045 Mostly successful MySQL database function tests
Two failures remain, at least one requiring query munging.
2018-12-21 10:14:26 -05:00
de07352fd0 Syntactic adjustments for MySQL
- "key", "read", and "rename" are reserved words
- CTEs in INSERTs must precede SELECT
- Empty sets are represented only by explicit null
2018-12-20 21:23:06 -05:00
393b4e95ad Adjustments for MySQL 8
The prototype had been tested against MariaDB
2018-12-20 20:50:56 -05:00
4ef36643a4 Proof-of-concept PDO MySQL driver
- Configuration options were added
- Non-transactional locking was added to the savepoint handlers
- Tests were adjusted for MySQL's reserved words
2018-12-20 18:06:28 -05:00
316ba941a2 Hopefully complete MySQL schema 2018-12-15 11:09:46 -05:00
86c16d3cb3 Merge branch 'master' into mysql 2018-12-15 09:22:47 -05:00
8d9d249b88 Merge branch 'pg' 2018-12-14 09:28:19 -05:00
50f92625ef Use PosgreSQL's existing general Unicode collation
All collations appear to be case-insensitive
2018-12-14 09:18:56 -05:00
17052d3232 Update PostgreSQL-related documentation 2018-12-13 20:08:35 -05:00
29e7c1f154 Fix coverage 2018-12-13 19:56:07 -05:00
2bebdd44cf Implementation of native PostgreSQL interface
Changes to the Database class were required to avoid outputting booleans
2018-12-13 19:47:51 -05:00
b52dadf345 Make existing PostgreSQL tests explicitly PDO tests 2018-12-12 12:42:40 -05:00
161f5f08f6 Proactively support SQLite 3.25 2018-12-12 12:21:28 -05:00
28f803dd28 Handle PostgreSQL connection errors 2018-12-12 11:15:07 -05:00
0f48ce6f37 Use a Unicode collation for SQLite 2018-12-11 14:14:32 -05:00
a8e6487001 Draft documentation 2018-12-10 19:13:48 -05:00
73729a6be8 Simplify database cleanup between tests 2018-12-10 13:17:04 -05:00
8dbf237626 Group PostgreSQL tests as slow 2018-12-10 12:39:09 -05:00
35d46d2913 Use persistent connections with PostgreSQL 2018-12-10 12:28:43 -05:00
913cf71620 Fix incorrect annotations 2018-12-07 20:36:20 -05:00
f6966659a9 Use smarter coverage executer; properly suppress stderr during CLI tests 2018-12-07 20:25:48 -05:00
d9629be662 Use smarter coverage executer; properly suppress stderr during CLI tests 2018-12-07 20:03:04 -05:00
0513b606c2 Merge master 2018-12-07 19:21:44 -05:00
b9272ea2eb Fix test failures in PHP 7.1 2018-12-07 15:34:47 -05:00
f7b9a2a6cf Fixes for PHPUnit 7 2018-12-07 13:49:49 -05:00
089f666de6 Fix PDO insert ID errors in PHP 7.1 2018-12-06 17:46:00 -05:00
cf896121b2 Style fixes 2018-12-05 17:28:11 -05:00
f2245861e3 Restore complete Database coverage
Also suppress PostgreSQL database function tests from normal coverage,
and add a "coverage:full" task to run them if needed.
2018-12-05 17:07:47 -05:00
51755a2ce6 Retire article field groups 2018-12-05 16:55:14 -05:00
0129965bbd Cover some missed code 2018-12-05 12:54:19 -05:00
22941f5ad1 Fix session tests
PostgreSQL now passes all tests. Connection and permission errors still
need to be accounted
for before the implementation is complete.
2018-12-05 12:07:45 -05:00
15301cd7dc Fix cleanup tests in PostgreSQL 2018-12-05 11:05:01 -05:00
258be1d54e Fix most PostgreSQL test failures
Reasons for failures included an unhandled error code, erroneous sorting
assumptions, and a broken computation of the next insert ID in tests

Five failures remain.
2018-12-05 09:05:43 -05:00
8fc31cfc40 Rewrite various queries to work in PostgreSQL
This involved changes to the driver interface as well as the database
schemata. The most significantly altered queries were for article
selection and marking, which relied upon unusual features of SQLite.
Overall query efficiency should not be adversely affected (it may have
even imprved) in the common case, while very rare cases (not presently
triggered by any REST handlers) require more queries.

One notable benefit of these changes is that functions which query
articles can now have complete control over which columns are returned.
This has not, however, been implemented yet: symbolic column groups are
still used for now.

Note that PostgreSQL still fails many tests, but the test suite runs to
completion. Note also that one line of the Database class is not
covered; later changes will eventually make it easier to cover the line
in question.
2018-12-04 20:41:21 -05:00
5c5a5a4886 Appease PostgreSQL's max() aggregate 2018-11-29 14:36:34 -05:00
527ecee393 Code coverage fixes 2018-11-29 13:56:15 -05:00
4a1c23ba45 Munge PostgreSQL queries instead of adding explicit casts
PDO does not adequately inform PostgreSQL of a parameter's type, so type
casts are required. Rather than adding these to each query manually, the
queries are instead processed to add type hints automatically.

Unfortunately the queries are processed rather naively; question-mark
characters in string constants, identifiers, regex patterns, or geometry
operators will break things spectacularly.
2018-11-29 13:45:37 -05:00
4c8d8f1a52 Provide PostgreSQL with an empty-set query for IN() clauses
Also satisfy PostgreSQL with some explicit casts
2018-11-28 17:18:33 -05:00
e68fcc0afa Manipulate only those sequences in the current PostgreSQL schema 2018-11-28 17:16:03 -05:00
4a2efd9987 Correct the state of PostgreSQL serial sequence during tests 2018-11-28 16:24:12 -05:00
dd4f22e04e Avoid use of reserved SQL word "user" 2018-11-28 14:21:36 -05:00
0f7baf4b51 Base MySQL schema
Note the columns "key" and "read" must be quoted in addition to "user".
2018-11-28 14:00:36 -05:00
10b228224d Correct PostgreSQL data format and other tweaks 2018-11-28 12:12:49 -05:00
8dfedd30ef Test PostgreSQL schema upgrade
This was in fact buggy due to the schema version check causing an error
2018-11-28 10:46:23 -05:00
d0db784b22 PostgreSQL schema tweak 2018-11-27 17:50:38 -05:00
93af381436 Test setting of schema name 2018-11-27 17:39:39 -05:00
1414f8979c Fix savepoint handling and locking in PostgreSQL driver 2018-11-27 17:16:00 -05:00
8a49202036 Use common cleanup code for all database-related tests 2018-11-27 14:26:33 -05:00
925560d4ba Cleanup 2018-11-25 00:06:20 -05:00
a75fad53ca Adapt the rest of the test series 2018-11-25 00:03:56 -05:00
dccd4caede Convert one database function test series (articles) to a common harness
Also revert the dropping of tables in the schema files. This was for the
convenience of tests, but the risk of data loss is too great
2018-11-24 23:18:17 -05:00
36c5984c47 Add drop statements to database schemata to simplify testing 2018-11-23 12:53:56 -05:00
7340d65c0e Make data clearing in tests static 2018-11-23 10:01:17 -05:00
39110858b7 Move database function test series as first step in re-organization 2018-11-23 09:29:06 -05:00
8c20411359 Align statement tests with other database driver tests 2018-11-22 23:18:20 -05:00
f22e53fdc9 Align result tests with driver tests 2018-11-22 19:55:54 -05:00
aa1b65b5d4 Take a different tack on shared database tests
Tests for different drivers will have their own files, but all derive
from a common prototype test series where applicable, similar to the
existing arrangement for database function tests. However, the prototype
will reside with other test cases rather than in the library path. The
database function test series will hopefully be moved as well in time.
2018-11-22 13:55:57 -05:00
8103d37bc7 Dev dependency update 2018-11-22 13:36:25 -05:00
736a8c9d0c Improved timeout handling for both SQlite and PostgreSQL 2018-11-22 13:30:13 -05:00
4e444fd86c Generic database interface creation in tests 2018-11-21 13:06:01 -05:00
c0c4810662 Nominally complete PostgreSQL driver
Connection error handling as well as uprade error handling still need
to be implemented.
2018-11-21 11:06:12 -05:00
84b4cb7465 Enable PostgreSQL statement testing
Tests involving binary data are skipped for now
2018-11-20 16:32:18 -05:00
d52af6db5a PostgreSQL fixes
Errors were not correctly throwing exceptions

For the sake of SQLite compatibility booleans should be bound as
integers in PDO
2018-11-20 15:48:03 -05:00
e2b6cb8360 Remove PicoFeed-related FIXMEs
PicoFeed will never be fixed, so they are not helpful
2018-11-20 15:46:22 -05:00
b5733b070c Clean up statement tests
PostgreSQL tests are suppressed for now, but most pass.
2018-11-20 15:45:20 -05:00
e30d82fbaa Correct signature 2018-11-16 21:35:05 -05:00
976672de5b Test cleanup 2018-11-16 21:32:27 -05:00
edfae438fa Refine pg connection strings 2018-11-16 21:20:54 -05:00
796315c00c Basic stub of PDO-base PostgreSQL driver 2018-11-10 00:02:38 -05:00
c63d24e125 Version bump 2018-11-09 23:57:33 -05:00
471dad0645 Fix PDO driver initialization 2018-11-09 20:27:05 -05:00
c4ca9149a1 Move Statement tests 2018-11-09 15:01:46 -05:00
7e11019e83 Consolidate statement tests 2018-11-09 14:58:10 -05:00
ea6b4c951e Skip Result tests when necessary 2018-11-09 14:56:30 -05:00
a3dbb08da9 Remove obsolete test code 2018-11-08 16:29:46 -05:00
30d6f6db37 Consolidate Db result test series into single file 2018-11-08 14:50:58 -05:00
ffea7e804a Sync Composer locks 2018-11-07 13:11:27 -05:00
467cc66cc8 Update changelog 2018-11-07 13:06:33 -05:00
3b3b810f10 Replicate some unspecified NCNv1 behaviour, and document the rest
See #139 for list

Closes #139
Closes #140
Closes #141
2018-11-07 13:01:46 -05:00
1dcbb56077 Changelog and documentation updates 2018-11-07 10:24:35 -05:00
63ae6fb703 Merge remote-tracking branch 'remotes/origin/user-rewrite' 2018-11-06 16:36:50 -05:00
3a4100576a Merge remote-tracking branch 'remotes/origin/cli-overhaul' 2018-11-06 16:36:35 -05:00
39134f5f7e Rest of CLI tests 2018-11-06 16:35:33 -05:00
4869559fb3 Test NCNv1 user query 2018-11-06 13:21:53 -05:00
2dd1b45d3e Cover the Query class with database tests for now 2018-11-06 12:51:34 -05:00
efac62f0e4 Add missing return type hints where possible 2018-11-06 12:50:26 -05:00
9e6f0460c2 Ensure the Lang class always exists when throwing exceptions 2018-11-06 12:48:31 -05:00
ba8e208d79 Partial CLI tests 2018-11-06 12:32:28 -05:00
1a8acdf03f Fix early exceptions 2018-11-06 09:04:51 -05:00
9428d7468a Add more user management functionality to the CLI 2018-11-05 09:08:50 -05:00
a8cc9a4780 Tests for internal user driver; closes #50 2018-11-04 12:06:30 -05:00
d40243a84c Clean up configuration setting in tests 2018-11-04 09:16:34 -05:00
5f775bef7a Appease phpdbg coverage bug 2018-11-03 13:49:02 -04:00
1ac85df46b Last set of tests for User class 2018-11-03 13:26:22 -04:00
b8f8a617fe Simply user test data providers
The user manager no longer differentiates between the internal driver
and other drivers, making the duplication unnecessary
2018-11-02 17:28:12 -04:00
a52b985826 Cover the Query class with database tests for now 2018-11-02 12:14:46 -04:00
ffa7bd5a5d Fix error in previous commit 2018-11-02 12:01:03 -04:00
931fe3b585 Move password generation to the User class
This allows user drivers which wish to generate their own passwords to
do so, and those which do not to defer to the built-in generator
2018-11-02 11:52:55 -04:00
31cdf313a4 Add missing return type hints where possible 2018-11-02 11:47:10 -04:00
1b8e1e499b Dev dependency update 2018-11-02 10:02:49 -04:00
5959c0672d Tests for most of the User class 2018-11-02 10:02:37 -04:00
898533bde5 More simplification
Authentication is now used as the primary point of synchronization
between the internal database and any external database
2018-11-02 10:01:49 -04:00
40d679844b Ensure the Lang class always exists when throwing exceptions 2018-10-31 14:32:11 -04:00
27edcddc9b Simplify NCNv1 userStatus call 2018-10-28 13:59:09 -04:00
057d72c816 Remove the distinction between internal and external user functionality 2018-10-28 13:50:57 -04:00
11747c93fd Strip out unused user management functionality
Tests have been removed as well; new tests are forthcoming
2018-10-28 10:59:17 -04:00
455 changed files with 39260 additions and 15227 deletions

22
.gitignore vendored
View file

@ -1,12 +1,26 @@
# Temporary files and dependencies
# Temporary files
/vendor/
/vendor-bin/*/vendor
/release/
/documentation/
/manual/
/tests/coverage/
/dist/arch/arsse/
/dist/arch/src/
/dist/arch/pkg/
/arsse.db*
/config.php
/.php_cs.cache
/.php-cs-fixer.cache
/tests/.phpunit.result.cache
/tests/.phpunit.cache
# Dependencies
/vendor/
/vendor-bin/*/vendor
/node_modules
/yarn.lock
/yarn-error.log
# Windows files
@ -22,7 +36,6 @@ $RECYCLE.BIN/
.DS_Store
.AppleDouble
.LSOverride
Icon
._*
.Spotlight-V100
.Trashes
@ -33,6 +46,7 @@ Icon
*.zip
*.7z
*.tar.gz
*.tar.xz
*.tgz
*.deb
*.rpm

79
.php-cs-fixer.dist.php Normal file
View file

@ -0,0 +1,79 @@
<?php
/** @license MIT
* Copyright 2017 J. King, Dustin Wilson et al.
* See LICENSE and AUTHORS files for details */
declare(strict_types=1);
namespace JKingWeb\Arsse;
const BASE = __DIR__.DIRECTORY_SEPARATOR;
$paths = [
__FILE__,
BASE."arsse.php",
BASE."RoboFile.php",
BASE."lib",
BASE."tests/cases",
BASE."tests/lib",
BASE."tests/bootstrap.php",
BASE."tests/server.php",
];
$rules = [
// house rules where PSR series is silent
'align_multiline_comment' => ['comment_type' => "phpdocs_only"],
'array_syntax' => ['syntax' => "short"],
'binary_operator_spaces' => [
'default' => "single_space",
'operators' => ['=>' => "align_single_space"],
],
'cast_spaces' => ['space' => "single"],
'concat_space' => ['spacing' => "none"],
'list_syntax' => ['syntax' => "short"],
'magic_constant_casing' => true,
'magic_method_casing' => true,
'modernize_types_casting' => true,
'native_function_casing' => true,
'native_function_type_declaration_casing' => true,
'no_binary_string' => true,
'no_blank_lines_after_phpdoc' => true,
'no_empty_comment' => true,
'no_empty_phpdoc' => true,
'no_empty_statement' => true,
'no_extra_blank_lines' => true, // this could probably use more configuration
'no_mixed_echo_print' => ['use' => "echo"],
'no_short_bool_cast' => true,
'no_trailing_comma_in_singleline_array' => true,
'no_unneeded_control_parentheses' => true,
'no_unneeded_curly_braces' => true,
'no_unused_imports' => true,
'no_whitespace_before_comma_in_array' => true,
'normalize_index_brace' => true,
'object_operator_without_whitespace' => true,
'pow_to_exponentiation' => true,
'set_type_to_cast' => true,
'standardize_not_equals' => true,
'trailing_comma_in_multiline' => ['elements' => ["arrays"]],
'unary_operator_spaces' => true,
'yoda_style' => false,
// PSR standard to apply
'@PSR12' => true,
// house exceptions to PSR rules
'curly_braces_position' => [
'functions_opening_brace' => "same_line",
'classes_opening_brace' => "same_line",
],
'function_declaration' => ['closure_function_spacing' => "none"],
'new_with_braces' => false, // no option to specify absence of braces
'php_unit_attributes' => true,
];
$finder = \PhpCsFixer\Finder::create();
foreach ($paths as $path) {
if (is_file($path)) {
$finder = $finder->append([$path]);
} else {
$finder = $finder->in($path);
}
}
return (new \PhpCsFixer\Config)->setRiskyAllowed(true)->setRules($rules)->setFinder($finder);

View file

@ -1,30 +0,0 @@
<?php
/** @license MIT
* Copyright 2017 J. King, Dustin Wilson et al.
* See LICENSE and AUTHORS files for details */
namespace JKingWeb\Arsse;
const BASE = __DIR__.DIRECTORY_SEPARATOR;
$paths = [
__FILE__,
BASE."arsse.php",
BASE."RoboFile.php",
BASE."lib",
BASE."tests",
];
$rules = [
'@PSR2' => true,
'braces' => ['position_after_functions_and_oop_constructs' => "same"],
];
$finder = \PhpCsFixer\Finder::create();
foreach ($paths as $path) {
if (is_file($path)) {
$finder = $finder->append([$path]);
} else {
$finder = $finder->in($path);
}
}
return \PhpCsFixer\Config::create()->setRules($rules)->setFinder($finder);

255
CHANGELOG
View file

@ -1,8 +1,246 @@
Version 0.10.6 (2024-12-27)
===========================
Bug fixes:
- Do not hang when language files are missing or corrupted
Changes:
- Support PHP 8.4
- Support MySQL 9.0
Version 0.10.5 (2024-01-10)
===========================
Changes:
- Require PHP 7.3
- Adapt the Arch package to make using alternative PHP interpreters easier
(see manual for details)
- Multiple editorial and stylistic changes to the UNIX manual page
Version 0.10.4 (2023-01-24)
===========================
Changes:
- Support PHP 8.2
Version 0.10.3 (2022-09-14)
===========================
Bug fixes:
- Return all removed articles when multiple statuses are requested in Miniflux
- Allow multiple date ranges in search strings in Tiny Tiny RSS
- Honour user time zone when interpreting search strings in Tiny Tiny RSS
- Perform MySQL table maintenance more reliably
- Address CVE-2022-31090, CVE-2022-31091, CVE-2022-29248, and CVE-2022-31109
Version 0.10.2 (2022-04-04)
===========================
Changes:
- Update Guzzle PSR-7 due to CVE-2022-24775
Version 0.10.1 (2022-01-17)
===========================
Changes:
- Support PHP 8.1
Version 0.10.0 (2021-07-11)
===========================
New features:
- Complete Unix manual page
- Support for running service as a forking daemon
- Respond to TERM and HUP signals when possible
Changes:
- Packages for Debian and related are now available (see manual for details)
Version 0.9.2 (2021-05-25)
==========================
Bug fixes:
- Do not fail adding users to an empty database (regression since 0.9.0)
- Cleanly ignore unknown configuration properties
- Set access mode to rw-r---- when creating SQLite databases
Changes:
- Packages for Arch Linux are now available (see manual for details)
- Numerous improvements to the manual
Version 0.9.1 (2021-03-18)
==========================
Bug fixes:
- Respond to PUT requests with 201 rather than 200 in Miniflux
Changes:
- Correct Web server configuration in manual
Version 0.9.0 (2021-03-06)
==========================
New features:
- Support for the Miniflux protocol (see manual for details)
- Support for API level 15 of Tiny Tiny RSS
- Support for feed icons in Fever
- Command-line functionality for managing user metadata
- Command-line functionality for managing Miniflux login tokens
Bug fixes:
- Further relax Fever HTTP correctness, to fix more clients
- Use icons specified in Atom feeds when available
- Do not return null as subscription unread count
- Explicitly forbid U+003A COLON and control characters in usernames, for
compatibility with RFC 7617
- Never return 401 in response to an OPTIONS request
- Accept "t" and "f" as booleans in Tiny Tiny RSS
Changes:
- Administrator account requirements for Nextcloud News functionality are
now enforced
- E_DEPRECATED is now suppressed for compatibility with PHP 8 until affected
dependencies can be replaced
Version 0.8.5 (2020-10-27)
==========================
Bug fixes:
- Relax Fever HTTP correctness, to fix some clients
- Add the QUERY_STRING FastCGI parameter to the sample Nginx configuration
Version 0.8.4 (2020-09-09)
==========================
Bug fixes:
- Don't crash updating feeds cached without ETag (regression since 0.8.3)
Version 0.8.3 (2020-02-16)
==========================
Changes:
- Officially require PHP 7.1 (accidentally required since version 0.8.0)
- Various internal changes pursuant to use of PHP 7.1
Version 0.8.2 (2019-12-07)
==========================
Bug fixes:
- Enforce foreign key constraints in MySQL
- Widen most text fields for MySQL
Version 0.8.1 (2019-10-28)
==========================
Bug fixes:
- Don't crash updating feeds cached solely via ETag
- Don't fail importing new folders from OPML files
- Don't fail adding a feed which collides with another via redirection
- Don't fail on very long text-search queries containing question marks
when using PostgreSQL or MySQL
- Specify HTTP authentication encoding as UTF-8
Changes:
- Include a user manual
- Normalize newsfeed URLs before checking for duplicates
Version 0.8.0 (2019-07-26)
==========================
New features:
- Support for the Fever protocol (see manual for details)
- Command line functionality for clearing a password, disabling the account
- Command line options for dealing with Fever passwords
- Command line functionality for importing and exporting OPML
- Command line documentation of all commands and options
Bug fixes:
- Treat command line option -h the same as --help
- Sort Tiny Tiny RSS special feeds according to special ordering
- Invalidate sessions when passwords are changed
- Correct example systemd unit to start after PostgreSQL and MySQL
Changes:
- Perform regular database maintenance to improve long-term performance
Version 0.7.1 (2019-03-25)
==========================
Bug fixes:
- Correctly initialize new on-disk SQLite databases
- Retry queries on schema changes with PDO SQLite
- Correctly read author name from database in Tiny Tiny RSS
- Update internal version number to correct version
Changes:
- Improve performance of lesser-used database queries
Version 0.7.0 (2019-03-02)
==========================
New features:
- Support for basic freeform searching in Tiny Tiny RSS
- Console command to refresh all stale feeds once then exit
Bug fixes:
- Ensure updating does not fail with newsfeeds larger than 250 entries
Version 0.6.1 (2019-01-23)
==========================
Bug fixes:
- Unify SQL timeout settings
- Correctly escape shell command in subprocess service driver
- Correctly allow null time intervals in configuration when appropriate
Changes:
- Change PicoFeed dependency to maintained version (Thanks, Aaron Parecki!)
- Remove non-functional cURL service driver
Version 0.6.0 (2019-01-21)
==========================
New features:
- Support for PostgreSQL databases
- Support for MySQL databases
- Validation of configuration parameters
Bug fixes:
- Use a general-purpose Unicode collation with SQLite databases
- Use the correct SQLite schema change procedure for 3.25 and later
Changes:
- Improve performance of common database queries by 80-90%
- Make configuration defaults consistent with their defined types
Version 0.5.1 (2018-11-10)
==========================
Bug fixes:
- Correctly initialize PDO database driver
Version 0.5.0 (2018-11-07)
==========================
New features:
- Command line functionality for listing, removing, and changing the password
of users as well as testing authentication
Bug fixes:
- Print command-line error messages more sensibly
- Allow exporting default configuration to standard output
- Fail correctly on authentication failure
- Prefer JSON data over GET parameters in Nextcloud News
Changes:
- Simplify user management backend to minimize opportunity for bugs
- Document previously unknown Nextcloud News behaviour
Version 0.4.0 (2018-10-26)
==========================
New features:
- Support for HTTP authentication in Tiny Tiny RSS (see README.md for details)
- Support for HTTP authentication in Tiny Tiny RSS (see manual for details)
- New userHTTPAuthRequired and userSessionEnforced settings
Version 0.3.1 (2018-07-22)
@ -13,7 +251,7 @@ Bug fixes:
- Minor fixes to code and documentation
Changes:
- Disable memory and time limits to avoid deadlocks with NextCloud News
- Disable memory and time limits to avoid deadlocks with Nextcloud News
Version 0.3.0 (2018-01-12)
==========================
@ -39,7 +277,7 @@ Bug fixes:
- Rename feeds correctly via TTRSS protocol
- Toggle marks correctly via TTRSS protocol
- Sort everything case-insensitively
- Be even stricter about output data types in NextCloud News
- Be even stricter about output data types in Nextcloud News
Changes:
- Do not omit read feeds from TTRSS' getCounters, to fix some clients
@ -48,13 +286,13 @@ Version 0.2.0 (2017-11-30)
==========================
New features:
- Support for the Tiny Tiny RSS protocol (see README.md for details)
- Support for the Tiny Tiny RSS protocol (see manual for details)
- Support for HTTP OPTIONS requests in all protocols
Bug fixes:
- Perform feed discovery *correctly*
- Expose the incorrectDbCharset boolean in the NextCloud News server status
- Give NextCloud News articles' guidHash attribute the correct type (string)
- Expose the incorrectDbCharset boolean in the Nextcloud News server status
- Give Nextcloud News articles' guidHash attribute the correct type (string)
Changes:
- Overhaul input type normalization to minimize bug opportunities
@ -63,11 +301,12 @@ Version 0.1.1 (2017-09-30)
==========================
Bug fixes:
- Perform feed discovery like NextCloud News does
- Perform feed discovery like Nextcloud News does
- Respond correctly to HEAD requests
- Various minor fixes
Version 0.1.0 (2017-08-29)
==========================
Initial release
New features:
- Initial release

218
README.md
View file

@ -1,187 +1,119 @@
# The Advanced RSS Environment
The Arsse is a news aggregator server which implements multiple synchronization protocols, including [version 1.2][NCNv1] of [NextCloud News][NCN]' protocol and the [Tiny Tiny RSS][TTRSS] protocol (details below). Unlike most other aggregator servers, The Arsse does not include a Web front-end (though one is planned as a separate project), and it relies on existing protocols to maximize compatibility with existing clients.
The Arsse is a news aggregator server, written in PHP, which implements multiple synchronization protocols. Unlike most other aggregator servers, The Arsse does not include a Web front-end (though one is planned as a separate project), and it relies on existing protocols to maximize compatibility with existing clients.
At present the software should be considered in an "alpha" state: though its core subsystems are covered by unit tests and should be free of major bugs, not everything has been rigorously tested. Additionally, many features one would expect from other similar software have yet to be implemented. Areas of future work include:
Information on how to install and use the software can be found in [the manual](https://thearsse.com/manual/), which is available online as well as with every copy of the software. This readme file instead focuses on how to set up a programming environment to modify the source code.
- Support for more database engines (PostgreSQL, MySQL, MariaDB)
- Providing more sync protocols (Google Reader, Fever, others)
- Complete tools for managing users
- Better packaging and configuration samples
# Installing from source
## Requirements
The main repository for The Arsse can be found at [code.mensbeam.com](https://code.mensbeam.com/MensBeam/arsse/), with a mirror also available [at GitHub](https://github.com/mensbeam/arsse/). The GitHub mirror does not accept bug reports, but the two should otherwise be equivalent.
The Arsse has the following requirements:
[Composer](https://getcomposer.org/) is required to manage PHP dependencies. After cloning the repository or downloading a source code tarball, running `composer install` will download all the required dependencies, and will advise if any PHP extensions need to be installed. If not installing as a programming environment, running `composer install -o --no-dev --no-scripts` is recommended.
- A Linux server utilizing systemd and Nginx (tested on Ubuntu 16.04)
- PHP 7.0.7 or later with the following extensions:
- [intl](http://php.net/manual/en/book.intl.php), [json](http://php.net/manual/en/book.json.php), [hash](http://php.net/manual/en/book.hash.php), and [pcre](http://php.net/manual/en/book.pcre.php)
- [dom](http://php.net/manual/en/book.dom.php), [simplexml](http://php.net/manual/en/book.simplexml.php), and [iconv](http://php.net/manual/en/book.iconv.php) (for picoFeed)
- [sqlite3](http://php.net/manual/en/book.sqlite3.php) or [pdo_sqlite](http://ca1.php.net/manual/en/ref.pdo-sqlite.php)
- Privileges to create and run daemon processes on the server
# Repository structure
## Installation
## Library code
At present, installation of The Arsse is rather manual. We hope to improve this in the future, but for now the steps below should help get you started. The instructions and configuration samples assume you will be using Ubuntu 16.04 (or equivalent Debian) and Nginx; we hope to expand official support for different configurations in the future as well.
The code which runs The Arsse, contained in `/arsse.php`, is only a short stub: the application itself is composed of the classes found under `/lib/`, with the main ones being:
### Initial setup
| Path | Description |
|----------------|---------------------------------------------------------|
| `CLI.php` | The command-line interface, including its documentation |
| `Conf.php` | Configuration handling |
| `Database.php` | High-level database interface |
| `Db/` | Low-level database abstraction layer |
| `REST/` | Protocol implementations |
| `REST.php` | General protocol handler for CORS, HTTP auth, etc. |
| `Arsse.php` | Singleton glueing the parts together |
1. Extract the tar archive to `/usr/share`
2. If desired, create `/usr/share/arsse/config.php` using `config.defaults.php` as a guide. The file you create only needs to contain non-default settings. The `userPreAuth` setting may be of particular interest
3. Copy `/usr/share/arsse/dist/arsse.service` to `/lib/systemd/system`
4. In a terminal, execute the following to start the feed fetching service:
The `/lib/Database.php` file is the heart of the application, performing queries on behalf of protocol implementations or the command-line interface.
``` sh
sudo systemctl enable arsse
sudo systemctl start arsse
```
Also necessary to the functioning of the application is the `/vendor/` directory, which contains PHP libraries which The Arsse depends upon. These are managed by Composer.
### Configuring the Web server and PHP
## Supporting data files
Sample configuration parameters for Nginx can be found in `arsse/dist/nginx.conf` and `arsse/dist/nginx-fcgi.conf`; the samples assume [a server group](http://nginx.org/en/docs/http/ngx_http_upstream_module.html#upstream) has already been defined for PHP. How to configure an Nginx service to use PHP and install the required PHP extensions is beyond the scope of this document, however.
The `/locale/` and `/sql/` directories contain human-language files and database schemata, both of which are occasionally used by the application in the course of execution. The `/www/` directory serves as a document root for a few static files to be made available to users by a Web server.
### Adding users
The `/dist/` directory, on the other hand, contains general and system-specific build files, manual pages, and samples of configuration for Web servers and other system integration. These are not used by The Arsse itself, but are used during the process of preparing new releases for supported operating systems.
The Arsse currently includes a `user add <username> [<password>]` console command to add users to the database; other user management tasks require manual database edits. Alternatively, if the Web server is configured to handle authentication, you may set the configuration option `userPreAuth` to `true` and The Arsse will defer to the server and automatically add any missing users as it encounters them.
## Documentation
## Installation from source
The source text for The Arsse's manual can be found in `/docs/`, with pages written in [Markdown](https://spec.commonmark.org/current/) and converted to HTML [with Daux](#building-the-manual). If a static manual is generated its files will appear under `/manual/`.
If installing from the Git repository rather than a download package, you will need to follow extra steps before the instructions in the section above.
The Arsse also has a UNIX manual page written in [mdoc](https://man.archlinux.org/man/extra/mandoc/mandoc_mdoc.7.en) format, which can be found under `/dist/man/`.
First, you must install [Composer] to fetch required PHP libraries. Once Composer is installed, dependencies may be downloaded with the following command:
In addition to the manuals the files `/README.md` (this file), `/CHANGELOG`, `/UPGRADING`, `/LICENSE`, and `/AUTHORS` also document various things about the software, rather than the software itself.
``` sh
php composer.phar install -o --no-dev --no-scripts
```
## Tests
Second, you may wish to create an example configuration file using the following command:
The `/tests/` directory contains everything related to automated testing. It is itself organized as follows:
``` sh
php ./arsse.php conf save-defaults "./config.defaults.php"
```
| Path | Description |
|--------------------|------------------------------------------------------------------------------------|
| `cases/` | The test cases themselves, organized in roughly the same structure as the code |
| `coverage/` | (optional) Generated code coverage reports |
| `docroot/` | Sample documents used in some tests, to be returned by PHP's basic HTTP server |
| `lib/` | Supporting classes which do not contain test cases |
| `bootstrap.php` | Bootstrap script, equivalent to `/arsse.php`, but for tests |
| `phpunit.dist.xml` | PHPUnit configuration file |
| `server.php` | Simple driver for the PHP HTTP server used during testing |
## License
PHPUnit's configuration can be customized by copying its configuration file to `/tests/phpunit.xml` and modifying the copy accordingly.
The Arsse is made available under the permissive MIT license. See the `LICENSE` and `AUTHORS` files included with the distribution or source code for exact legal text and copyright holders. Dependencies included in the distribution may be governed by other licenses.
## Tooling
## Contributing
The `/vendor-bin/` directory houses the files needed for the tools used in The Arsse's programming environment. These are managed by the Composer ["bin" plugin](https://github.com/bamarni/composer-bin-plugin) and are not used by The Arsse itself. The following files are also related to various programming tools:
Please refer to `CONTRIBUTING.md` for guidelines on contributing code to The Arsse.
| Path | Description |
|---------------------------|----------------------------------------------------------|
| `/.gitattributes` | Git settings for handling files |
| `/.gitignore` | Git file exclusion patterns |
| `/.php-cs-fixer.dist.php` | Configuration for [php-cs-fixer](https://cs.symfony.com) |
| `/.php-cs-fixer.cache` | Cache for php-cs-fixer |
| `/composer.json` | Configuration for Composer |
| `/composer.lock` | Version synchronization data for Composer |
| `/RoboFile.php` | Task definitions for [Robo](https://robo.li/) |
| `/robo` | Simple wrapper for executing Robo on POSIX systems |
| `/robo.bat` | Simple wrapper for executing Robo on Windows |
## Protocol compatibility notes
In addition the files `/package.json` and `/postcss.config.js` as well as the `/node_modules/` directory are used by [Yarn](https://yarnpkg.com/) and [PostCSS](https://postcss.org/) when modifying the stylesheet for The Arsse's manual.
### General
# Common tasks
#### Type casting
We use a tool called [Robo](https://robo.li/) to simplify the execution of common tasks. It is installed with The Arsse's other dependencies, and its configured tasks can be listed by executing `./robo` without arguments.
The Arsse does not guarantee it will handle type casting of input in the same way as reference implementations for its supported protocols. As a general rule, clients should endeavour to send only correct input.
## Running tests
The Arsse does, however, guarantee _output_ to be of the same type. If it is not, this is [a bug][newIssue] and should be reported.
The Arsse has an extensive [PHPUnit](https://phpunit.de/) test suite; tests can be run by executing `./robo test`, which can be supplemented with any arguments understoof by PHPUnit. For example, to test only the Tiny Tiny RSS protocol, one could run `./robo test --testsuite TTRSS`.
#### Content sanitization
There is also a `test:quick` Robo task which excludes slower tests, and a `test:full` task which includes redundant tests in addition to the standard test suite
The Arsse makes use of the [picoFeed] newsfeed parsing library to sanitize article content. The exact sanitization parameters may differ from those of reference implementations for protocols The Arsse supports.
### Test coverage
### NextCloud News v1.2
Computing the coverage of tests can be done by running `./robo coverage`, after which an HTML-format coverage report will be written to `/tests/coverage/`. Either [PCOV](https://github.com/krakjoe/pcov) or [Xdebug](https://xdebug.org) is required for this. PCOV is generally recommended as it is faster than Xdebug. Neither extension need be enabled globally; Robo will enable it when needed.
As a general rule, The Arsse should yield the same output as the reference implementation for all valid inputs (otherwise you've found [a bug][newIssue]), but there are exception, either because the NextCloud News (hereafter "NCN") [protocol description][NCNv1] is at times ambiguous or incomplete, or because implementation details necessitate it differ; this section along with the General section above detail these differences.
## Enforcing coding style
#### Differences
The [php-cs-fixer](https://cs.symfony.com) tool, executed via `./robo clean`, can be used to rewrite code to adhere to The Arsse's coding style. The style largely follows [PSR-12](https://www.php-fig.org/psr/psr-12/) with some exceptions:
- Article GUID hashes are not hashes like in NCN; they are integers rendered as strings
- Article fingerprints are a combination of hashes rather than a single hash
- When marking articles as starred the feed ID is ignored, as they are not needed to establish uniqueness
- The feed updater ignores the `userId` parameter: feeds in The Arsse are deduplicated, and have no owner
- The `/feeds/all` route lists only feeds which should be checked for updates, and it also returns all `userId` attributes as empty strings: feeds in The Arsse are deduplicated, and have no owner
- The updater console commands mentioned in the protocol specification are not implemented, as The Arsse does not implement the required NextCloud subsystems
- The `lastLoginTimestamp` attribute of the user metadata is always the current time: The Arsse's implementation of the protocol is fully stateless
- Classes, methods, and functions should have their opening brace on the same line as the signature
- Anonymous functions should have no space before the parameter list
#### Ambiguities
## Building the manual
- NCN specifies that GET parameters are treated "the same" as request body parameters; it does not specify what to do in cases where they conflict. The Arsse chooses to give GET parameters precedence
- NCN does not define validity of folder and names other than to specify that the empty string is invalid. The Arsse further considers any string composed only of whitesapce to be invalid
- NCN does not specify a return code for bulk-marking operations without a `newestItemId` provided; The Arsse returns `422`
- NCN does not specify what should be done when creating a feed in a folder which does not exist; the Arsse adds the feed to the root folder
- NCN does not specify what should be done when moving a feed to a folder which does not exist; The Arsse return `422`
- NCN does not specify what should be done when renaming a feed to an invalid title, nor what constitutes an invalid title; The Arsse uses the same rules as it does for folders, and returns `422` in cases of rejection
The Arsse's user manual, made using [Daux](https://daux.io/), can be compiled by running `./robo manual`, which will output files to `/manual/`. It is also possible to serve the manual from a test HTTP server on port 8085 by running `./robo manual:live`.
### Tiny Tiny RSS
### Rebuilding the manual theme
As a general rule, The Arsse should yield the same output as the reference implementation for all valid inputs (otherwise you've found [a bug][newIssue]), but there are exception, either because the Tiny Tiny RSS (hereafter "TTRSS") [protocol description][TTRSS] is incomplete, erroneous, or out of date, or because TTRSS itself is buggy, or because implementation details necessitate The Arsse differ; this section along with the General section above detail these differences.
The manual employs a custom theme derived from the standard Daux theme. If the standard Daux theme receives improvements, the custom theme can be rebuilt by running `./robo manual:theme`. This requires that [NodeJS](https://nodejs.org) and [Yarn](https://yarnpkg.com/) be installed, but JavaScript tools are not required to modify The Arsse itself, nor the content of the manual.
#### Extended functionality
## Packaging a release
The Arsse supports both [the set of extensions][ext-feedreader] to the TTRSS protocol defined by [FeedReader], as well as [the `getCompactHeadlines` operation][ext-newsplus] defined by [News+].
Producing release packages is done by running `./robo package`. This performs the following operations:
We are not aware of any other extensions to the TTRSS protocol. If you know of any more, please [let us know][newIssue].
#### Missing features
- The `getPref` operation is not implemented; it returns `UNKNOWN_METHOD`
- The `shareToPublished` operation is not implemented; it returns `UNKNOWN_METHOD`
- Setting an article's "published" flag with the `updateArticle` operation is not implemented and will gracefully fail
- The `search` parameter of the `getHeadlines` operation is not implemented; the operation will proceed as if no search string were specified
- The `sanitize`, `force_update`, and `has_sandbox` parameters of the `getHeadlines` operation are ignored
- String `feed_id` values for the `getCompactHeadlines` operation are not supported and will yield an `INCORRECT_USAGE` error
- Articles are limited to a single attachment rather than multiple attachments
#### Differences
- Input that cannot be parsed as JSON normally returns a `NOT_LOGGED_IN` error; The Arsse returns a non-standard `MALFORMED_INPUT` error instead
- Feed, category, and label names are normally unrestricted; The Arsse rejects empty strings, as well as strings composed solely of whitespace
- Discovering multiple feeds during `subscribeToFeed` processing normally produces an error; The Arsse instead chooses the first feed it finds
- Providing the `setArticleLabel` operation with an invalid label normally silently fails; The Arsse returns an `INVALID_USAGE` error instead
- Article hashes are normally SHA1; The Arsse uses SHA256 hashes
- Article attachments normally have unique IDs; The Arsse always gives attachments an ID of `"0"`
- The default sort order of the `getHeadlines` operation normally uses custom sorting for "special" feeds; The Arsse's default sort order is equivalent to `feed_dates` for all feeds
- The `getCounters` operation normally omits members with zero unread; The Arsse includes everything to appease some clients
#### Other notes
- TTRSS accepts base64-encoded passwords, though this is undocumented; The Arsse accepts base64-encoded passwords as well
- TTRSS sometimes returns an incorrect count from the `setArticleLabel` operation; The Arsse returns a correct count in all cases
- TTRSS sometimes returns out-of-date cached information; The Arsse does not use caches as TTRSS does, so information is always current
- TTRSS returns results for _feed_ ID `-3` when providing the `getHeadlines` operation with _category_ ID `-3`; The Arsse retuns the correct results
- The protocol doucmentation advises not to use `limit` or `skip` together with `unread_only` for the `getFeeds` operation as it produces unpredictable results; The Arsse produces predictable results by first retrieving all unread feeds and then applying `skip` and `limit`
- The protocol documentation on values for the `view_mode` parameter of the `getHeadlines` operation is out of date; The Arsse matches the actual implementation and supports the undocumented `published` and `has_note` values exposed by the Web user interface
- The protocol documentation makes mention of a `search_mode` parameter for the `getHeadlines` operation, but this seems to be ignored; The Arsse does not implement it
- The protocol documentation makes mention of an `output_mode` parameter for the `getCounters` operation, but this seems to be ignored; The Arsse does not implement it
- The documentation for the `getCompactHeadlines` operation states the default value for `limit` is 20, but the reference implementation defaults to unlimited; The Arsse also defaults to unlimited
- It is assumed TTRSS exposes undocumented behaviour; unless otherwise noted The Arsse only implements documented behaviour
#### Interaction with HTTP authentication
Tiny Tiny RSS itself is unaware of HTTP authentication: if HTTP authentication is used in the server configuration, it has no effect on authentication in the API. The Arsse, however, makes use of HTTP authentication for NextCloud News, and can do so for TTRSS as well. In a default configuration The Arsse functions in the same way as TTRSS: HTTP authentication and API authentication are completely separate and independent. Behaviour is summarized below:
- With default settings:
- Clients may optionally provide HTTP credentials; API authentication then proceeds as normal
- All feed icons are visible to unauthenticated clients
- If the `userHTTPAuthRequired` setting is `true`:
- Clients must pass HTTP authentication; API authentication then proceeds as normal
- Feed icons are visible only to their owners
- If the `userSessionEnforced` setting is `false`:
- Clients may optionally provide HTTP credentials; if they are valid API authentication is skipped: tokens are issued upon login, but ignored for HTTP-authenticated requests
- All feed icons are visible to unauthenticated clients
- If the `userHTTPAuthRequired` setting is `true` and the `userSessionEnforced` setting is `false`:
- Clients must pass HTTP authentication; API authentication is skipped: tokens are issued upon login, but thereafter ignored
- Feed icons are visible only to their owners
- If the `userPreAuth` setting is `true`:
- The Web server asserts authentication was successful; API authentication only checks that HTTP and API user names match
- Feed icons are visible only to their owners
- If the `userPreAuth` setting is `true` and the `userSessionEnforced` setting is `false`:
- The Web server asserts authentication was successful; API authentication is skipped: tokens are issued upon login, but thereafter ignored
- Feed icons are visible only to their owners
In all cases, supplying invalid HTTP credentials will result in a 401 response.
[newIssue]: https://code.mensbeam.com/MensBeam/arsse/issues/new
[Composer]: https://getcomposer.org/
[picoFeed]: https://github.com/miniflux/picoFeed/
[NCN]: https://github.com/nextcloud/news
[NCNv1]: https://github.com/nextcloud/news/blob/master/docs/externalapi/Legacy.md
[CORS]: https://fetch.spec.whatwg.org/#http-cors-protocol
[TTRSS]: https://git.tt-rss.org/git/tt-rss/wiki/ApiReference
[FeedReader]: https://jangernert.github.io/FeedReader/
[News+]: https://github.com/noinnion/newsplus/
[ext-feedreader]: https://github.com/jangernert/FeedReader/tree/master/data/tt-rss-feedreader-plugin
[ext-newsplus]: https://github.com/hrk/tt-rss-newsplus-plugin
- Duplicates a [Git](https://git-scm.com/) working tree with the commit (usually a release tag) to package
- Generates the HTML manual
- Installs runtime Composer dependencies with an optimized autoloader
- Deletes numerous unneeded files
- Exports the default configuration of The Arsse to a file
- Compresses the remaining files into a tarball

View file

@ -2,12 +2,23 @@
use Robo\Result;
class RoboFile extends \Robo\Tasks {
const BASE = __DIR__.\DIRECTORY_SEPARATOR;
const BASE_TEST = self::BASE."tests".\DIRECTORY_SEPARATOR;
const BASE = __DIR__.\DIRECTORY_SEPARATOR;
const BASE_TEST = BASE."tests".\DIRECTORY_SEPARATOR;
define("IS_WIN", defined("PHP_WINDOWS_VERSION_MAJOR"));
define("IS_MAC", php_uname("s") === "Darwin");
define("IS_LINUX", !IS_WIN && !IS_MAC);
error_reporting(0);
/**
* Runs the typical test suite
function norm(string $path): string {
$out = realpath($path);
if (!$out) {
$out = str_replace(["/", "\\"], \DIRECTORY_SEPARATOR, $path);
}
return $out;
}
class RoboFile extends \Robo\Tasks {
/** Runs the typical test suite
*
* Arguments passed to the task are passed on to PHPUnit. Thus one may, for
* example, run the following command and get the expected results:
@ -15,28 +26,27 @@ class RoboFile extends \Robo\Tasks {
* ./robo test --testsuite TTRSS --exclude-group slow --testdox
*
* Please see the PHPUnit documentation for available options.
*/
*/
public function test(array $args): Result {
return $this->runTests("php", "typical", $args);
return $this->runTests(escapeshellarg(\PHP_BINARY), "typical", $args);
}
/**
* Runs the full test suite
/** Runs the full test suite
*
* This includes pedantic tests which may help to identify problems.
* See help for the "test" task for more details.
*/
*/
public function testFull(array $args): Result {
return $this->runTests("php", "full", $args);
return $this->runTests(escapeshellarg(\PHP_BINARY), "full", $args);
}
/**
* Runs a quick subset of the test suite
*
* See help for the "test" task for more details.
*/
*/
public function testQuick(array $args): Result {
return $this->runTests("php", "quick", $args);
return $this->runTests(escapeshellarg(\PHP_BINARY), "quick", $args);
}
/** Produces a code coverage report
@ -45,19 +55,34 @@ class RoboFile extends \Robo\Tasks {
* tests/coverage/. Additional reports may be produced by passing
* arguments to this task as one would to PHPUnit.
*
* Robo first tries to use phpdbg and will fall back to Xdebug if available.
* Because Xdebug slows down non-coverage tasks, however, phpdbg is highly
* recommended if debugging facilities are not otherwise needed.
*/
* Robo first tries to use pcov and will fall back to xdebug.
* Neither pcov nor xdebug need to be enabled to be used; they
* only need to be present in the extension load path to be used.
*/
public function coverage(array $args): Result {
// run tests with code coverage reporting enabled
$exec = $this->findCoverageEngine();
return $this->runTests($exec, "typical", array_merge(["--coverage-html", self::BASE_TEST."coverage"], $args));
return $this->runTests($exec, "coverage", array_merge(["--coverage-html", BASE_TEST."coverage"], $args));
}
/** Runs the coding standards fixer */
/** Produces a code coverage report, with redundant tests
*
* Depending on the environment, some tests that normally provide
* coverage may be skipped, while working alternatives are normally
* suppressed for reasons of time. This coverage report will try to
* run all tests which may cover code.
*
* See also help for the "coverage" task for more details.
*/
public function coverageFull(array $args): Result {
// run tests with code coverage reporting enabled
$exec = $this->findCoverageEngine();
return $this->runTests($exec, "typical", array_merge(["--coverage-html", BASE_TEST."coverage"], $args));
}
/** Runs the coding-style fixer */
public function clean($opts = ['demo|d' => false]): Result {
$t = $this->taskExec(realpath(self::BASE."vendor/bin/php-cs-fixer"));
$t = $this->taskExec(norm(BASE."vendor-bin/csfixer/vendor/bin/php-cs-fixer"));
$t->arg("fix");
if ($opts['demo']) {
$t->args("--dry-run", "--diff")->option("--diff-format", "udiff");
@ -65,84 +90,644 @@ class RoboFile extends \Robo\Tasks {
return $t->run();
}
/** Finds the first suitable means of computing code coverage, either pcov or xdebug. */
protected function findCoverageEngine(): string {
$null = null;
$code = 0;
exec("phpdbg --version", $null, $code);
if (!$code) {
return "phpdbg -qrr";
$dir = rtrim(ini_get("extension_dir"), "/").\DIRECTORY_SEPARATOR;
$ext = IS_WIN ? "dll" : "so";
$php = escapeshellarg(\PHP_BINARY);
$code = escapeshellarg(BASE."lib");
if (extension_loaded("pcov")) {
return "$php -d pcov.enabled=1 -d pcov.directory=$code";
} elseif (extension_loaded("xdebug")) {
return "$php -d xdebug.mode=coverage";
} elseif (file_exists($dir."pcov.$ext")) {
return "$php -d extension=pcov.$ext -d pcov.enabled=1 -d pcov.directory=$code";
} elseif (file_exists($dir."xdebug.$ext")) {
return "$php -d zend_extension=xdebug.$ext -d xdebug.mode=coverage";
} else {
return "php";
return $php;
}
}
protected function runTests(string $executor, string $set, array $args) : Result {
/** Returns the necessary shell arguments to print error output or all output to the bitbucket
*
* @param bool $all Whether all output (true) or only error output (false) should be suppressed
*/
protected function blackhole(bool $all = false): string {
$hole = IS_WIN ? "nul" : "/dev/null";
return $all ? ">$hole 2>&1" : "2>$hole";
}
/** Executes PHPUnit, used by the test and coverage tasks.
*
* This also executes the built-in PHP Web server, which is required to fetch some newsfeeds during tests
*
* @param string $executor The path to the PHP binary to execute with any required extra arguments. Normally this is either "php" or the result of findCoverageEngine()
* @param string $set The set of tests to run, either "typical" (excludes redundant tests), "quick" (excludes redundant and slow tests), "coverage" (excludes tests not needed for coverage), or "full" (all tests)
* @param array $args Extra arguments passed by Robo from the command line
*/
protected function runTests(string $executor, string $set, array $args): Result {
switch ($set) {
case "typical":
$set = ["--exclude-group", "optional"];
$exc = ["optional"];
break;
case "quick":
$set = ["--exclude-group", "optional,slow"];
$exc = ["optional", "slow"];
break;
case "coverage":
$exc = ["optional", "coverageOptional"];
break;
case "full":
$set = [];
$exc = [];
break;
default:
throw new \Exception;
}
$execpath = realpath(self::BASE."vendor-bin/phpunit/vendor/phpunit/phpunit/phpunit");
$confpath = realpath(self::BASE_TEST."phpunit.xml");
$this->taskServer(8000)->host("localhost")->dir(self::BASE_TEST."docroot")->rawArg("-n")->arg(self::BASE_TEST."server.php")->background()->run();
return $this->taskExec($executor)->arg($execpath)->option("-c", $confpath)->args(array_merge($set, $args))->run();
$extra = ["--display-phpunit-deprecations"];
foreach ($exc as $group) {
$extra[] = "--exclude-group";
$extra[] = $group;
}
$execpath = norm(BASE."vendor-bin/phpunit/vendor/phpunit/phpunit/phpunit");
$confpath = realpath(BASE_TEST."phpunit.dist.xml") ?: norm(BASE_TEST."phpunit.xml");
$this->taskServer(8000)->host("localhost")->dir(BASE_TEST."docroot")->rawArg("-n")->arg(BASE_TEST."server.php")->rawArg($this->blackhole())->background()->run();
return $this->taskExec($executor)->option("-d", "zend.assertions=1")->arg($execpath)->option("-c", $confpath)->args(array_merge($extra, $args))->run();
}
/** Returns a Git version string for a given Git tree-ish ID
*
* Returns an array containing the tree-ish string and the version string.
*
* @param string|null $commit The tree-ish ID. If not supplied the user will be prompted
*/
protected function commitVersion(?string $commit): array {
$target = $commit ?? $this->askDefault("Reference commit:", "HEAD");
$base = escapeshellarg(BASE);
$blackhole = $this->blackhole();
// get useable version strings from Git
$version = trim(`git -C $base describe --tags $target $blackhole`);
if (!$version) {
throw new \Exception("Commit reference invalid");
}
return [$target, $version];
}
/** Checks whether all the supplied terminal commands are available in path */
protected function toolExists(string ...$binary): bool {
$blackhole = $this->blackhole(IS_WIN);
foreach ($binary as $bin) {
if (!exec(escapeshellarg($bin)." --help $blackhole", $junk, $status) || $status) {
return false;
}
}
return true;
}
/** Packages a given commit of the software into a release tarball
*
* The version to package may be any Git tree-ish identifier: a tag, a branch,
* The commit to package may be any Git tree-ish identifier: a tag, a branch,
* or any commit hash. If none is provided on the command line, Robo will prompt
* for a commit to package; the default is "head".
* for a commit to package; the default is "HEAD".
*
* Note that while it is possible to re-package old versions, the resultant tarball
* may not be equivalent due to subsequent changes in the exclude list, or because
* of new tooling.
*/
public function package(string $version = null): Result {
*/
public function packageGeneric(?string $commit = null): Result {
if (!$this->toolExists("git")) {
throw new \Exception("Git is required in PATH to produce generic release tarballs");
}
// establish which commit to package
$version = $version ?? $this->askDefault("Commit to package:", "head");
$archive = self::BASE."arsse-$version.tar.gz";
[$commit, $version] = $this->commitVersion($commit);
preg_match('/^([^-]+)(?:-(\d+)-(\w+))?$/', $version, $m);
$archVersion = $m[1].($m[2] ? ".r$m[2].$m[3]" : "");
$baseVersion = $m[1];
$release = $m[2];
// name the generic release tarball
$tarball = BASE."release/$version/arsse-$version.tar.gz";
// start a collection
$t = $this->collectionBuilder();
// create a temporary directory
$dir = $t->tmpDir().\DIRECTORY_SEPARATOR;
// create a Git worktree for the selected commit in the temp location
$t->taskExec("git worktree add ".escapeshellarg($dir)." ".escapeshellarg($version));
// perform Composer installation in the temp location
$t->taskComposerInstall()->dir($dir)->noDev()->optimizeAutoloader()->arg("--no-scripts");
// delete unwanted files
$t->taskFilesystemStack()->remove([
$dir.".git",
$dir.".gitignore",
$dir.".gitattributes",
$dir."composer.json",
$dir."composer.lock",
$dir.".php_cs.dist",
$dir."phpdoc.dist.xml",
$dir."build.xml",
$dir."RoboFile.php",
$dir."CONTRIBUTING.md",
$dir."tests",
$dir."vendor-bin",
$dir."robo",
$dir."robo.bat",
]);
// generate a sample configuration file
$t->taskExec("php arsse.php conf save-defaults config.defaults.php")->dir($dir);
// package it all up
$t->taskPack($archive)->addDir("arsse", $dir);
$result = $this->taskExec("git worktree add ".escapeshellarg($dir)." ".escapeshellarg($version))->dir(BASE)->run();
if ($result->getExitCode() > 0) {
return $result;
}
try {
// Perform Arch-specific tasks
if (file_exists($dir."dist/arch")) {
// patch the Arch PKGBUILD file with the correct version string
$t->addTask($this->taskReplaceInFile($dir."dist/arch/PKGBUILD")->regex('/^pkgver=.*$/m')->to("pkgver=$archVersion"));
// patch the Arch PKGBUILD file with the correct source file
$t->addTask($this->taskReplaceInFile($dir."dist/arch/PKGBUILD")->regex('/^source=\("arsse-[^"]+"\)$/m')->to('source=("'.basename($tarball).'")'));
}
// perform Debian-specific tasks
if (file_exists($dir."dist/debian")) {
// generate the Debian changelog; this also validates our original changelog
$changelog = $this->changelogParse(file_get_contents($dir."CHANGELOG"), $version);
$debianChangelog = $this->changelogDebian($changelog, $version);
// save the Debian-format changelog
$t->addTask($this->taskWriteToFile($dir."dist/debian/changelog")->text($debianChangelog));
}
// perform RPM-specific tasks
if (file_exists($dir."dist/rpm")) {
// patch the spec file with the correct version and release
$t->addTask($this->taskReplaceInFile($dir."dist/rpm/arsse.spec")->regex('/^Version: .*$/m')->to("Version: $baseVersion"));
$t->addTask($this->taskReplaceInFile($dir."dist/rpm/arsse.spec")->regex('/^Release: .*$/m')->to("Release: $release"));
// patch the spec file with the correct tarball name
$t->addTask($this->taskReplaceInFile($dir."dist/rpm/arsse.spec")->regex('/^Source0: .*$/m')->to("Source0: arsse-$version.tar.gz"));
// append the RPM changelog to the spec file
$t->addTask($this->taskWriteToFile($dir."dist/rpm/arsse.spec")->append(true)->text("\n\n%changelog\n".$this->changelogRPM($changelog, $version)));
}
// save commit description to VERSION file for reference
$t->addTask($this->taskWriteToFile($dir."VERSION")->text($version));
if (file_exists($dir."docs") || file_exists($dir."manpages/en.md")) {
// perform Composer installation in the temp location with dev dependencies to include Robo and Daux
$t->addTask($this->taskExec("composer install")->arg("-q")->dir($dir));
if (file_exists($dir."docs")) {
// generate the HTML manual
$t->addTask($this->taskExec("./robo manual -q")->dir($dir));
}
if (file_exists($dir."manpages/en.md")) {
// generate manpages (NOTE: obsolete process)
$t->addTask($this->taskExec("./robo manpage")->dir($dir));
}
}
// perform Composer installation in the temp location for final output
$t->addTask($this->taskExec("composer install")->dir($dir)->arg("--no-dev")->arg("-o")->arg("--no-scripts")->arg("-q"));
// delete unwanted files
$t->addTask($this->taskFilesystemStack()->remove([
$dir.".git",
$dir.".gitignore",
$dir.".gitattributes",
$dir."dist/debian/.gitignore",
$dir."composer.json",
$dir."composer.lock",
$dir.".php_cs.dist",
$dir."phpdoc.dist.xml",
$dir."build.xml",
$dir."RoboFile.php",
$dir."CONTRIBUTING.md",
$dir."docs",
$dir."manpages",
$dir."tests",
$dir."vendor-bin",
$dir."vendor/bin",
$dir."robo",
$dir."robo.bat",
$dir."package.json",
$dir."yarn.lock",
$dir."postcss.config.js",
]));
$t->addCode(function() use ($dir) {
// Remove files which lintian complains about; they're otherwise harmless
$files = [];
foreach (new \CallbackFilterIterator(new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($dir."vendor", \FilesystemIterator::CURRENT_AS_PATHNAME | \FilesystemIterator::SKIP_DOTS)), function($v, $k, $i) {
return preg_match('/\/\.git(?:ignore|attributes|modules)$/D', $v);
}) as $f) {
$files[] = $f;
}
return $this->taskFilesystemStack()->remove($files)->run();
});
// generate a sample configuration file
$t->addTask($this->taskExec(escapeshellarg(\PHP_BINARY)." arsse.php conf save-defaults config.defaults.php")->dir($dir));
// remove any existing archive
$t->addTask($this->taskFilesystemStack()->remove($tarball));
// package it all up
$t->addTask($this->taskFilesystemStack()->mkdir(dirname($tarball)));
$t->addTask($this->taskPack($tarball)->addDir("arsse", $dir));
// execute the collection
$result = $t->run();
} finally {
// remove the Git worktree
$this->taskFilesystemStack()->remove($dir)->run();
$this->taskExec("git worktree prune")->dir(BASE)->run();
}
return $result;
}
/** Packages a release tarball into a Debian source package
*
* The commit to package may be any Git tree-ish identifier: a tag, a branch,
* or any commit hash. If none is provided on the command line, Robo will prompt
* for a commit to package; the default is "HEAD".
*/
public function packageDebsrc(?string $commit = null): Result {
// establish which commit to package
[$commit, $version] = $this->commitVersion($commit);
$tarball = BASE."release/$version/arsse-$version.tar.gz";
// determine the base version (i.e. x.y.z) and the Debian version (i.e. x.y.z-a)
preg_match('/^(\d+(?:\.\d+)+)(?:-(\d+))?/', $version, $m);
$baseVersion = $m[1];
$debVersion = $m[1]."-".($version === $baseVersion ? "1" : $m[2]);
// start a task collection and create a temporary directory
$t = $this->collectionBuilder();
$dir = $t->tmpDir().\DIRECTORY_SEPARATOR;
// build the generic release tarball if it doesn't exist
if (!file_exists($tarball)) {
$t->addTask($this->taskExec(BASE."robo package:generic $commit"));
}
$base = $dir."arsse-$version".\DIRECTORY_SEPARATOR;
// start by extracting the tarball
$t->addCode(function() use ($tarball, $dir, $base) {
// Robo's extract task is broken, so we do it manually
(new \Archive_Tar($tarball))->extract($dir, false);
return $this->taskFilesystemStack()->rename($dir."arsse", $base)->run();
});
// re-pack the tarball using a specific name special to Debian
$t->addTask($this->taskPack($dir."arsse_$baseVersion.orig.tar.gz")->addDir("arsse-$baseVersion", $base));
// pack the debian tarball
$t->addTask($this->taskPack($dir."arsse_$debVersion.debian.tar.gz")->addDir("debian", $base."dist/debian"));
// generate the DSC file
$t->addCode(function() use ($t, $debVersion, $baseVersion, $dir, $base) {
try {
$dsc = $this->generateDebianSourceControl($base."dist/debian/", $debVersion, [$dir."arsse_$baseVersion.orig.tar.gz", $dir."arsse_$debVersion.debian.tar.gz"]);
} catch (\Exception $e) {
return new Result($t, 1, $e->getMessage());
}
// write the DSC file
return $this->taskWriteToFile($dir."arsse_$debVersion.dsc")->text($dsc)->run();
});
// delete any existing files
$t->AddTask($this->taskFilesystemStack()->remove([BASE."release/$version/arsse_$baseVersion.orig.tar.gz", BASE."release/$version/arsse_$debVersion.debian.tar.gz", BASE."release/$version/arsse_$debVersion.dsc"]));
// copy the new files over
$t->addTask($this->taskFilesystemStack()->copy($dir."arsse_$baseVersion.orig.tar.gz", BASE."release/$version/arsse_$baseVersion.orig.tar.gz")->copy($dir."arsse_$debVersion.debian.tar.gz", BASE."release/$version/arsse_$debVersion.debian.tar.gz")->copy($dir."arsse_$debVersion.dsc", BASE."release/$version/arsse_$debVersion.dsc"));
return $t->run();
}
/** Packages a given commit of the software and produces all relevant release files
*
* The commit to package may be any Git tree-ish identifier: a tag, a branch,
* or any commit hash. If none is provided on the command line, Robo will prompt
* for a commit to package; the default is "HEAD".
*
* In addition to the release tarball, a Debian source package, Arch PKGBUILD,
* and RPM spec file are output as well. These are suitable for use with Open
* Build Service instances and with slight modification the Arch User Repository.
* Use for Launchpad PPAs has not been tested.
*/
public function package(?string $commit = null): Result {
if (!$this->toolExists("git")) {
throw new \Exception("Git is required in PATH to produce packages");
}
[$commit, $version] = $this->commitVersion($commit);
$tarball = BASE."release/$version/arsse-$version.tar.gz";
// build the generic release tarball
$result = $this->taskExec(BASE."robo package:generic $commit")->run();
if (!$result->wasSuccessful()) {
return $result;
}
// if the generic tarball could be built, try to produce Arch, Debian, and RPM files; these might legitimately not exist in old releases
// start by getting the list of files from the tarball
$archive = new \Archive_Tar($tarball);
$filelist = array_flip(array_column($archive->listContent(), "filename"));
// start a collection
$t = $this->collectionBuilder();
// Produce an Arch PKGBUILD if appropriate
if (isset($filelist['arsse/dist/arch/PKGBUILD'])) {
$t->addCode(function() use ($tarball, $archive) {
$dir = dirname($tarball).\DIRECTORY_SEPARATOR;
$archive->extractList("arsse/dist/arch/PKGBUILD", $dir, "arsse/dist/arch/", false);
// update the tarball's checksum
$sums = [
'md5' => hash_file("md5", $tarball),
];
return $this->taskReplaceInFile($dir."PKGBUILD")->regex('/^md5sums=\("SKIP"\)$/m')->to('md5sums=("'.$sums['md5'].'")')->run();
});
}
// Produce a Debian source package if appropriate
if (isset($filelist['arsse/dist/debian/control']) && isset($filelist['arsse/dist/debian/source/format'])) {
$t->addTask($this->taskExec(BASE."robo package:debsrc $commit"));
}
// Produce an RPM spec file if appropriate
if (isset($filelist['arsse/dist/rpm/arsse.spec'])) {
$t->addCode(function() use ($tarball, $archive) {
$dir = dirname($tarball).\DIRECTORY_SEPARATOR;
$archive->extractList("arsse/dist/rpm/arsse.spec", $dir, "arsse/dist/rpm/", false);
// perform a do-nothing filesystem operation since we need a Robo task result
return $this->taskFilesystemStack()->chmod($dir."arsse.spec", 0644)->run();
});
}
return $t->run();
}
/** Generates static HTML manual pages in the "manual" directory
*
* The resultant files are suitable for offline viewing and inclusion into release builds
*/
public function manual(array $args): Result {
$execpath = escapeshellarg(norm(BASE."vendor-bin/daux/vendor/bin/daux"));
$t = $this->collectionBuilder();
$t->taskExec($execpath)->arg("generate")->option("-d", BASE."manual")->args($args);
$t->taskDeleteDir(BASE."manual/daux_libraries");
$t->taskDeleteDir(BASE."manual/theme");
$t->taskDeleteDir(BASE."manual/themes/src");
return $t->run();
}
/** Serves a live view of the manual using the built-in Web server */
public function manualLive(array $args): Result {
$execpath = escapeshellarg(norm(BASE."vendor-bin/daux/vendor/bin/daux"));
return $this->taskExec($execpath)->arg("serve")->args($args)->run();
}
/** Rebuilds the entire manual theme
*
* This requires Node and Yarn to be installed, and only needs to be done when
* Daux's theme changes
*/
public function manualTheme(array $args): Result {
if (!$this->toolExists("yarn")) {
throw new \Exception("Yarn is required in PATH to update the Daux theme");
}
$postcss = escapeshellarg(norm(BASE."node_modules/.bin/postcss"));
$themesrc = norm(BASE."docs/theme/src/").\DIRECTORY_SEPARATOR;
$themeout = norm(BASE."docs/theme/arsse/").\DIRECTORY_SEPARATOR;
$dauxjs = norm(BASE."vendor-bin/daux/vendor/daux/daux.io/themes/daux/js/").\DIRECTORY_SEPARATOR;
// start a collection; this stops after the first failure
$t = $this->collectionBuilder();
// install dependencies via Yarn
$t->taskExec("yarn install");
// compile the stylesheet
$t->taskExec($postcss)->arg($themesrc."arsse.scss")->option("-o", $themeout."arsse.css");
// copy JavaScript files from the Daux theme
foreach (glob($dauxjs."daux*.js") as $file) {
$t->taskFilesystemStack()->copy($file, $themeout.basename($file), true);
}
// execute the collection
$out = $t->run();
// clean the Git worktree list
$this->_exec("git worktree prune");
return $t->run();
}
/** Parses the contents of the CHANGELOG file into an array structure
*
* This is done line-by-line and tends to be quite strict.
* The parsed output can be used to generate changelogs in other formats,
* such as a Debian changelog or RPM changelog.
*
* @param string $text The text of the CHANGELOG file
* @param string $targetVersion The x.y.z version number of the latest release. This is used to check that version numbers and dates have been updated when preparing a release
*/
protected function changelogParse(string $text, string $targetVersion): array {
$lines = preg_split('/\r?\n/', $text);
$version = "";
$section = "";
$out = [];
$entry = [];
$expected = ["version"];
for ($a = 0; $a < sizeof($lines);) {
$l = rtrim($lines[$a++]);
if (in_array("version", $expected) && preg_match('/^Version ([\d\?]+(?:\.[\d\?]+)*) \(([\d\?]{4}-[\d\?]{2}-[\d\?]{2})\)\s*$/D', $l, $m)) {
$version = $m[1];
if (!preg_match('/^\d{4}-\d{2}-\d{2}$/D', $m[2])) {
// uncertain dates are allowed only for the top version, and only if it does not match the target version (otherwise we have forgotten to set the correct date before tagging)
if (!$out && $targetVersion !== $version) {
// use today's date; local time is fine
$date = date("Y-m-d");
} else {
throw new \Exception("CHANGELOG: Date at line $a is incomplete");
}
} else {
$date = $m[2];
}
if ($entry) {
$out[] = $entry;
}
$entry = ['version' => $version, 'date' => $date, 'features' => [], 'fixes' => [], 'changes' => []];
$expected = ["separator"];
} elseif (in_array("separator", $expected) && preg_match('/^=+/', $l)) {
$length = strlen($lines[$a - 2]);
if (strlen($l) !== $length) {
throw new \Exception("CHANGELOG: Separator at line $a is of incorrect length");
}
$expected = ["blank line"];
$section = "";
} elseif (in_array("blank line", $expected) && $l === "") {
$expected = [
'' => ["features section", "fixes section", "changes section"],
'features' => ["fixes section", "changes section", "version"],
'fixes' => ["changes section", "version"],
'changes' => ["version"],
][$section];
$expected[] = "end-of-file";
} elseif (in_array("features section", $expected) && $l === "New features:") {
$section = "features";
$expected = ["item"];
} elseif (in_array("fixes section", $expected) && $l === "Bug fixes:") {
$section = "fixes";
$expected = ["item"];
} elseif (in_array("changes section", $expected) && $l === "Changes:") {
$section = "changes";
$expected = ["item"];
} elseif (in_array("item", $expected) && preg_match('/^- (\w.*)$/D', $l, $m)) {
$entry[$section][] = $m[1];
$expected = ["item", "continuation", "blank line"];
} elseif (in_array("continuation", $expected) && preg_match('/^ (\S.*)$/D', $l, $m)) {
$last = sizeof($entry[$section]) - 1;
$entry[$section][$last] .= "\n".$m[1];
} else {
if (sizeof($expected) > 1) {
throw new \Exception("CHANGELOG: Expected one of [".implode(", ", $expected)."] at line $a");
} else {
throw new \Exception("CHANGELOG: Expected ".$expected[0]." at line $a");
}
}
}
if (!in_array("end-of-file", $expected)) {
if (sizeof($expected) > 1) {
throw new \Exception("CHANGELOG: Expected one of [".implode(", ", $expected)."] at end of file");
} else {
throw new \Exception("CHANGELOG: Expected ".$expected[0]." at end of file");
}
}
$out[] = $entry;
return $out;
}
/** Produce a Debian changelog from a parsed CHANGELOG file
*
* The Debian changelog format is extremely specific with certain tokens
* having special meaning and leading whitespace also being significant.
* Modifying this function should be done with extreme care.
*
* @param array $log The parsed chaneglog, output by changelogParse()
* @param string $targetVersion The second output of commitVersion()
*/
protected function changelogDebian(array $log, string $targetVersion): string {
$authorName = "J. King";
$authorMail = "jking@jkingweb.ca";
$latest = $log[0]['version'];
$baseVersion = preg_replace('/^(\d+(?:\.\d+)*).*/', "$1", $targetVersion);
if ($baseVersion !== $targetVersion && version_compare($latest, $baseVersion, ">")) {
// if the changelog contains an entry for a future version, change its version number to match the target version instead of using the future version
$log[0]['version'] = $targetVersion;
$log[0]['distribution'] = "UNRELEASED";
} elseif ($baseVersion !== $targetVersion) {
// otherwise synthesize a changelog entry for the changes since the last tag
array_unshift($log, ['version' => $targetVersion, 'date' => date("Y-m-d"), 'features' => [], 'fixes' => [], 'changes' => ["Unspecified changes"], 'distribution' => "UNRELEASED"]);
}
$out = "";
foreach ($log as $entry) {
// normalize the version string
preg_match('/^(\d+(?:\.\d+)*)(?:-(\d+)-.+)?$/D', $entry['version'], $m);
$version = $m[1]."-".($m[2] ?: "1");
// output the entry
$out .= "arsse ($version) ".($entry['distribution'] ?? "unstable")."; urgency=low\n";
if ($entry['features']) {
$out .= "\n";
foreach ($entry['features'] as $item) {
$out .= " * ".trim(preg_replace("/^/m", " ", $item))."\n";
}
}
if ($entry['fixes']) {
$out .= "\n";
foreach ($entry['fixes'] as $item) {
$out .= " * ".trim(preg_replace("/^/m", " ", $item))."\n";
}
}
if ($entry['changes']) {
$out .= "\n";
foreach ($entry['changes'] as $item) {
$out .= " * ".trim(preg_replace("/^/m", " ", $item))."\n";
}
}
$out .= "\n -- $authorName <$authorMail> ".\DateTimeImmutable::createFromFormat("Y-m-d", $entry['date'], new \DateTimeZone("UTC"))->format("D, d M Y")." 00:00:00 +0000\n\n";
}
return $out;
}
/** Produces a Debian "source control" file from various bits of data
*
* As with a Debian changelog, the output is of a very exacting format,
* and this function should be modified with care.
*
* @param string $dir The path to Debian-specific files, with trailing slash
* @param string $version The Debian version string, in the format x.y.z-a
* @param array $tarballs An array of paths to the "orig" and "debian" tarball files
*/
protected function generateDebianSourceControl(string $dir, string $version, array $tarballs): string {
// read in control file
if (!$control = @file_get_contents($dir."control")) {
throw new \Exception("Unable to read Debian control file");
}
// read the format
if (!$format = @file_get_contents($dir."source/format")) {
throw new \Exception("Unable to read source format in Debian files");
}
// read the binary packages from the control file
if (preg_match_all('/^Package:\s*(\S+)/m', $control, $m)) {
$binary = [];
foreach ($m[1] as $pkg) {
$binary[] = $pkg;
}
} else {
throw new \Exception("No packages defined in Debian control file");
}
// read the package architectures from the control file
if (preg_match_all('/^Architecture:\s*(\S+)/m', $control, $m) || sizeof($m[1]) != sizeof($binary)) {
$architecture = [];
foreach ($m[1] as $pkg) {
$architecture[] = preg_replace('/\s/', "", $pkg);
}
} else {
throw new \Exception("Number of architectures defined in Debian control file does not match number of packages");
}
// read the package sections from the control file
if (preg_match_all('/^Section:\s*(\S+)/m', $control, $m) || sizeof($m[1]) != sizeof($binary)) {
$section = [];
foreach ($m[1] as $pkg) {
$section[] = $pkg;
}
} else {
throw new \Exception("Number of sections defined in Debian control file does not match number of packages");
}
// read the package priorities from the control file
if (preg_match_all('/^Priority:\s*(\S+)/m', $control, $m) || sizeof($m[1]) != sizeof($binary)) {
$priority = [];
foreach ($m[1] as $pkg) {
$priority[] = $pkg;
}
} else {
throw new \Exception("Number of priorities defined in Debian control file does not match number of packages");
}
// read simple metadata from the control file
$metadata = [];
foreach (["Source", "Maintainer", "Homepage", "Standards-Version", "Vcs-Browser", "Vcs-Git"] as $meta) {
if (preg_match('/^'.$meta.':\s*(.+)/m', $control, $m)) {
$metadata[$meta] = $m[1];
} else {
throw new \Exception("$meta is not defined in Debian control file");
}
}
// read build dependencies from control file
if (preg_match('/(?:^|\n)Build-Depends:\s*((?:[^\n]|\n(?= ))+)/s', $control, $m)) {
$buildDepends = preg_replace('/\s/', "", $m[1]);
} else {
$buildDepends = "";
}
// trim format
$format = trim($format);
// consolidate binaries and package list
$packageList = [];
for ($a = 0; $a < sizeof($binary); $a++) {
$packageList[] = "$binary[$a] deb $section[$a] $priority[$a] arch=$architecture[$a]";
}
$packageList = implode("\n ", $packageList);
// consolidate package names
$binary = implode(",", $binary);
// consolidate architectures
$architecture = implode(",", array_unique($architecture));
// calculate checksums for files
$fMeta = [];
foreach ($tarballs as $f) {
$fMeta[$f] = [
'name' => basename($f),
'size' => filesize($f),
'sha1' => hash_file("sha1", $f),
'sha256' => hash_file("sha256", $f),
'md5' => hash_file("md5", $f),
];
}
// consolidate SHA-1 checksums
$sums = [];
foreach ($fMeta as $data) {
$sums[] = $data['sha1']." ".$data['size']." ".$data['name'];
}
$sumsSha1 = implode("\n ", $sums);
// consolidate SHA-256 checksums
$sums = [];
foreach ($fMeta as $data) {
$sums[] = $data['sha256']." ".$data['size']." ".$data['name'];
}
$sumsSha256 = implode("\n ", $sums);
// consolidate MD5 checksums
$sums = [];
foreach ($fMeta as $data) {
$sums[] = $data['md5']." ".$data['size']." ".$data['name'];
}
$sumsMd5 = implode("\n ", $sums);
// return complete file
return <<< DSC_FILE
Format: $format
Source: {$metadata['Source']}
Binary: $binary
Architecture: $architecture
Version: $version
Maintainer: {$metadata['Maintainer']}
Homepage: {$metadata['Homepage']}
Standards-Version: {$metadata['Standards-Version']}
Vcs-Browser: {$metadata['Vcs-Browser']}
Vcs-Git: {$metadata['Vcs-Git']}
Build-Depends: $buildDepends
Package-List:
$packageList
Checksums-Sha1:
$sumsSha1
Checksums-Sha256:
$sumsSha256
Files:
$sumsMd5
DSC_FILE;
}
}

113
UPGRADING
View file

@ -1,35 +1,126 @@
General upgrade notes
=====================
When upgrading between any two versions of The Arsse, the following are usually prudent:
When upgrading between any two versions of The Arsse, the following are
usually prudent:
- Back up your database
- Check for any changes to sample Web server configuration
- Check for any changes to sample systemd unit or other init files
- If installing from source, update dependencies with `composer install -o --no-dev`
- If installing from source, update dependencies with:
`composer install -o --no-dev`
Upgrading from 0.10.4 to 0.10.5
=============================
- PHP 7.3 is now required
- Web server configuration in the Arch Linux package has been modified to ease
the use of alternative PHP interpreters; please review the sample
configuration files for changes
Upgrading from 0.10.2 to 0.10.3
=============================
- The following Composer dependencies have been removed:
- laminas/laminas-diactoros
Upgrading from 0.8.5 to 0.9.0
=============================
- The database schema has changed from rev6 to rev7; if upgrading the database
manually, apply the 6.sql file
- Web server configuration has changed to accommodate Miniflux; the following
URL paths are affected:
- /v1/
- /version
- /healthcheck
- Icons for existing feeds in Miniflux and Fever will only appear once the
feeds in question have been fetched and parsed after upgrade. This may take
some time to occur depending on how often the feed is updated
- An administrator account is now required to refresh feeds via the
Nextcloud News protocol
Upgrading from 0.8.4 to 0.8.5
=============================
- The sample configuration for Nginx has changed, to correct the omission of
the QUERY_STRING FastCGI parameter in those passed to PHP. The omission
affects the Fever protocol in particular (the parameter is required for
Fever to function at all), though it could potentially affect some
Nextcloud News clients as well
Upgrading from 0.8.2 to 0.8.3
=============================
- PHP 7.1 is now required
- The following Composer dependencies have been added:
- nicolus/picofeed
- laminas/laminas-diactoros
- laminas/laminas-httphandlerrunner
- The following Composer dependencies have been removed:
- p3k/picofeed
- zendframework/zend-diactoros
- zendframework/zend-httphandlerrunner
Upgrading from 0.8.1 to 0.8.2
=============================
- The database schema has changed from rev5 to rev6; if upgrading the database
manually, apply the 5.sql file
Upgrading from 0.7.1 to 0.8.0
=============================
- The database schema has changed from rev4 to rev5; if upgrading the database
manually, apply the 4.sql file
- Web server configuration has changed to accommodate Fever; the following URL
paths are affected:
- /fever/
- The following Composer dependencies have been added:
- zendframework/zend-diactoros (version 2.x)
- zendframework/zend-httphandlerrunner
Upgrading from 0.5.1 to 0.6.0
=============================
- The database schema has changed from rev3 to rev4; if upgrading the database
manually, apply the 3.sql file
- Configuration is now validated for type and semantics: some previously
working configurations may no longer be accepted
Upgrading from 0.2.1 to 0.3.0
=============================
- The following Composer dependencies have been added:
- zendframework/zend-diactoros
- psr/http-message
- zendframework/zend-diactoros
- psr/http-message
Upgrading from 0.2.0 to 0.2.1
=============================
- The database schema has changed from rev2 to rev3; if upgrading the database manually, apply the 2.sql file
- The database schema has changed from rev2 to rev3; if upgrading the database
manually, apply the 2.sql file
Upgrading from 0.1.x to 0.2.0
=============================
- The database schema has changed from rev1 to rev2; if upgrading the database manually, apply the 1.sql file
- Web server configuration has changed to accommodate Tiny Tiny RSS; the following URL paths are affected:
- /tt-rss/api/
- /tt-rss/feed-icons/
- /tt-rss/images/
- The database schema has changed from rev1 to rev2; if upgrading the database
manually, apply the 1.sql file
- Web server configuration has changed to accommodate Tiny Tiny RSS; the
following URL paths are affected:
- /tt-rss/api/
- /tt-rss/feed-icons/
- /tt-rss/images/
- The following Composer dependencies have been added:
- jkingweb/druuid
- jkingweb/druuid

View file

@ -3,6 +3,8 @@
* Copyright 2017 J. King, Dustin Wilson et al.
* See LICENSE and AUTHORS files for details */
declare(strict_types=1);
namespace JKingWeb\Arsse;
const BASE = __DIR__.DIRECTORY_SEPARATOR;
@ -12,19 +14,21 @@ require_once BASE."vendor".DIRECTORY_SEPARATOR."autoload.php";
ignore_user_abort(true);
ini_set("memory_limit", "-1");
ini_set("max_execution_time", "0");
// NOTE: While running in the wild we don't want to spew deprecation warnings if users are ahead of us in PHP versions
error_reporting(\E_ALL & ~\E_DEPRECATED);
if (\PHP_SAPI=="cli") {
// initialize the CLI; this automatically handles --help and --version
if (\PHP_SAPI === "cli") {
// initialize the CLI; this automatically handles --help and --version else
Arsse::$obj = new Factory;
$cli = new CLI;
// handle other CLI requests; some do not require configuration
$cli->dispatch();
$exitStatus = $cli->dispatch();
exit($exitStatus);
} else {
// load configuration
$conf = file_exists(BASE."config.php") ? new Conf(BASE."config.php") : new Conf;
Arsse::load($conf);
Arsse::bootstrap();
// handle Web requests
$emitter = new \Zend\Diactoros\Response\SapiEmitter();
$emitter = new \Laminas\HttpHandlerRunner\Emitter\SapiEmitter;
$response = (new REST)->dispatch();
$emitter->emit($response);
}

View file

@ -18,22 +18,39 @@
],
"require": {
"php": "^7.0",
"php": ">=7.3",
"ext-intl": "*",
"ext-json": "*",
"ext-hash": "*",
"fguillot/picofeed": ">=0.1.31",
"hosteurope/password-generator": "^1.0",
"docopt/docopt": "^1.0",
"jkingweb/druuid": "^3.0",
"zendframework/zend-diactoros": "^1.6"
"ext-filter": "*",
"ext-dom": "*",
"nicolus/picofeed": "dev-php84",
"hosteurope/password-generator": "1.*",
"docopt/docopt": "dev-master",
"jkingweb/druuid": "3.*",
"guzzlehttp/psr7": "2.*",
"laminas/laminas-xml": "dev-fixup as 1.4.0",
"laminas/laminas-httphandlerrunner": "2.*"
},
"require-dev": {
"bamarni/composer-bin-plugin": "*"
},
"scripts": {
"post-install-cmd": ["@composer bin all install"],
"post-update-cmd": ["@composer bin all update"]
"suggest": {
"ext-pcntl": "To respond to signals, particularly to reload configuration via SIGHUP"
},
"config": {
"platform": {
"php": "7.3.33"
},
"allow-plugins": {
"bamarni/composer-bin-plugin": true
}
},
"extra": {
"bamarni-bin": {
"bin-links": false,
"forward-command": true
}
},
"autoload": {
"psr-4": {
@ -42,7 +59,23 @@
},
"autoload-dev": {
"psr-4": {
"JKingWeb\\Arsse\\Test\\": "tests/lib/"
"JKingWeb\\Arsse\\Test\\": "tests/lib/",
"JKingWeb\\Arsse\\TestCase\\": "tests/cases/"
}
}
},
"repositories": [
{
"type": "vcs",
"url": "https://github.com/JKingweb/picoFeed-1/"
},
{
"type": "vcs",
"url": "https://github.com/JKingweb/laminas-xml/"
},
{
"type": "vcs",
"url": "https://github.com/mensbeam/docopt.php/"
}
]
}

1114
composer.lock generated

File diff suppressed because it is too large Load diff

5
dist/apache/arsse-fcgi.conf vendored Normal file
View file

@ -0,0 +1,5 @@
ProxyPreserveHost On
ProxyFCGISetEnvIf "true" SCRIPT_FILENAME "/usr/share/arsse/arsse.php"
ProxyFCGISetEnvIf "-n req('Authorization')" HTTP_AUTHORIZATION "%{req:Authorization}"
ProxyPass "unix:/var/run/php/arsse.sock|fcgi://localhost/usr/share/arsse/"

34
dist/apache/arsse-loc.conf vendored Normal file
View file

@ -0,0 +1,34 @@
# Nextcloud News protocol
<Location "/index.php/apps/news/api">
Include "/etc/arsse/apache/arsse-fcgi.conf"
</Location>
# Tiny Tiny RSS protocol
<Location "/tt-rss/api">
Include "/etc/arsse/apache/arsse-fcgi.conf"
</Location>
# Tiny Tiny RSS feed icons
<Location "/tt-rss/feed-icons">
Include "/etc/arsse/apache/arsse-fcgi.conf"
</Location>
# Fever protocol
<Location "/fever">
Include "/etc/arsse/apache/arsse-fcgi.conf"
</Location>
# Miniflux protocol
<Location "/v1">
Include "/etc/arsse/apache/arsse-fcgi.conf"
</Location>
# Miniflux version number
<Location "/version">
Include "/etc/arsse/apache/arsse-fcgi.conf"
</Location>
# Miniflux "health check"
<Location "/healthcheck">
Include "/etc/arsse/apache/arsse-fcgi.conf"
</Location>

6
dist/apache/arsse.conf vendored Normal file
View file

@ -0,0 +1,6 @@
DocumentRoot "/usr/share/arsse/www"
<Directory "/usr/share/arsse/www">
Require all granted
</Directory>
Include "/etc/arsse/apache/arsse-loc.conf"

9
dist/apache/example.conf vendored Normal file
View file

@ -0,0 +1,9 @@
<VirtualHost *:443>
ServerName "news.example.com"
SSLEngine On
SSLCertificateFile "/etc/letsencrypt/live/news.example.com/fullchain.pem"
SSLCertificateKeyFile "/etc/letsencrypt/live/news.example.com/privkey.pem"
Include "/etc/arsse/apache/arsse.conf"
</VirtualHost>

64
dist/arch/PKGBUILD vendored Normal file
View file

@ -0,0 +1,64 @@
# Maintainer: J. King <jking@jkingweb.ca>
pkgname="arsse"
pkgver=0.10.4
pkgrel=1
epoch=
pkgdesc="Multi-protocol RSS/Atom newsfeed synchronization server"
arch=("any")
url="https://thearsse.com/"
license=("MIT")
conflicts=("arsse-git")
depends=()
makedepends=()
checkdepends=()
optdepends=("nginx: HTTP server"
"apache>=2.4: HTTP server"
"percona-server: Alternate database"
"postgresql>=10: Alternate database"
"php-pgsql-interpreter>=7.3: PostgreSQL database support")
backup=("etc/webapps/arsse/config.php"
"etc/webapps/arsse/systemd-environment"
"etc/php/php-fpm.d/arsse.conf"
"etc/php-legacy/php-fpm.d/arsse.conf"
"etc/webapps/arsse/nginx/example.conf"
"etc/webapps/arsse/nginx/arsse.conf"
"etc/webapps/arsse/nginx/arsse-loc.conf"
"etc/webapps/arsse/nginx/arsse-fcgi.conf"
"etc/webapps/arsse/apache/example.conf"
"etc/webapps/arsse/apache/arsse.conf"
"etc/webapps/arsse/apache/arsse-fcgi.conf"
"etc/webapps/arsse/apache/arsse-loc.conf")
source=("arsse-0.10.4.tar.gz")
md5sums=("SKIP")
package() {
# define runtime dependencies
depends=("php-interpreter>=7.3" "php-sqlite-interpreter>=7.3" "php-fpm-interpreter>=7.3")
# create most directories necessary for the final package
cd "$pkgdir"
mkdir -p "usr/share/webapps/arsse" "usr/share/doc/arsse" "usr/share/licenses/arsse" "usr/lib/systemd/system" "usr/lib/sysusers.d" "usr/lib/tmpfiles.d" "etc/php/php-fpm.d" "etc/php-legacy/php-fpm.d" "etc/webapps/arsse"
# copy requisite files
cd "$srcdir/arsse"
cp -r lib locale sql vendor www CHANGELOG UPGRADING README.md arsse.php "$pkgdir/usr/share/webapps/arsse"
cp -r manual/* "$pkgdir/usr/share/doc/arsse"
cp LICENSE AUTHORS "$pkgdir/usr/share/licenses/arsse"
cp dist/sysuser.conf "$pkgdir/usr/lib/sysusers.d/arsse.conf"
cp dist/tmpfiles.conf "$pkgdir/usr/lib/tmpfiles.d/arsse.conf"
cp dist/php-fpm.conf "$pkgdir/etc/php/php-fpm.d/arsse.conf"
cp -r dist/man "$pkgdir/usr/share"
cp -r dist/nginx dist/apache config.defaults.php "$pkgdir/etc/webapps/arsse"
# copy files requiring special permissions
install -Dm640 dist/config.php "$pkgdir/etc/webapps/arsse"
# patch generic configuration files to use Arch-specific paths and identifiers
sed -i -se 's/\/\(etc\|usr\/share\)\/arsse\//\/\1\/webapps\/arsse\//g' "$pkgdir/etc/webapps/arsse/nginx/"* "$pkgdir/etc/webapps/arsse/apache/"* "$pkgdir/usr/lib/tmpfiles.d/arsse.conf"
sed -i -se 's/\/var\/run\/php\//\/run\/php-fpm\//g' "$pkgdir/etc/webapps/arsse/nginx/"* "$pkgdir/etc/webapps/arsse/apache/"* "$pkgdir/etc/php/php-fpm.d/arsse.conf"
sed -i -se 's/www-data/http/g' "$pkgdir/etc/php/php-fpm.d/arsse.conf"
# make a duplicate PHP-FPM pool for php-legacy
sed -se 's/php-fpm/php-fpm-legacy/' "$pkgdir/etc/php/php-fpm.d/arsse.conf" > "$pkgdir/etc/php-legacy/php-fpm.d/arsse.conf"
# copy Arch-specific versions of files
install -Dm755 dist/arch/arsse "$pkgdir/usr/bin/arsse"
cp dist/arch/nginx-arsse-fcgi.conf "$pkgdir/etc/webapps/arsse/nginx/arsse-fcgi.conf"
cp dist/arch/apache-arsse-fcgi.conf "$pkgdir/etc/webapps/arsse/apache/arsse-fcgi.conf"
cp dist/arch/*.service "$pkgdir/usr/lib/systemd/system"
cp dist/arch/systemd-environment "$pkgdir/etc/webapps/arsse/systemd-environment"
}

79
dist/arch/PKGBUILD-git vendored Normal file
View file

@ -0,0 +1,79 @@
# Maintainer: J. King <jking@jkingweb.ca>
pkgname="arsse-git"
pkgver=0.10.4
pkgrel=1
epoch=
pkgdesc="Multi-protocol RSS/Atom newsfeed synchronization server, bugfix-testing version"
arch=("any")
url="https://thearsse.com/"
license=("MIT")
provides=("arsse")
conflicts=("arsse")
depends=("php-interpreter>=7.3" "php-intl-interpreter>=7.3" "php-sqlite-interpreter>=7.3")
makedepends=("composer")
checkdepends=()
optdepends=("nginx: HTTP server"
"apache>=2.4: HTTP server"
"percona-server: Alternate database"
"postgresql>=10: Alternate database"
"php-pgsql-interpreter>=7.3: PostgreSQL database support")
backup=("etc/webapps/arsse/config.php"
"etc/webapps/arsse/systemd-environment"
"etc/php/php-fpm.d/arsse.conf"
"etc/php-legacy/php-fpm.d/arsse.conf"
"etc/webapps/arsse/nginx/example.conf"
"etc/webapps/arsse/nginx/arsse.conf"
"etc/webapps/arsse/nginx/arsse-loc.conf"
"etc/webapps/arsse/nginx/arsse-fcgi.conf"
"etc/webapps/arsse/apache/example.conf"
"etc/webapps/arsse/apache/arsse.conf"
"etc/webapps/arsse/apache/arsse-fcgi.conf"
"etc/webapps/arsse/apache/arsse-loc.conf")
source=("git+https://code.mensbeam.com/MensBeam/arsse/")
md5sums=("SKIP")
pkgver() {
cd "arsse"
git describe --tags | sed 's/\([^-]*-g\)/r\1/;s/-/./g'
}
build() {
cd "$srcdir/arsse"
composer install
./robo manual
composer install --no-dev -o --no-scripts
php arsse.php conf save-defaults config.defaults.php
rm -r vendor/bin
}
package() {
# define runtime dependencies
depends=("php-interpreter>=7.3" "php-sqlite-interpreter>=7.3" "php-fpm-interpreter>=7.3")
# create most directories necessary for the final package
cd "$pkgdir"
mkdir -p "usr/share/webapps/arsse" "usr/share/doc/arsse" "usr/share/licenses/arsse" "usr/lib/systemd/system" "usr/lib/sysusers.d" "usr/lib/tmpfiles.d" "etc/php/php-fpm.d" "etc/php-legacy/php-fpm.d" "etc/webapps/arsse"
# copy requisite files
cd "$srcdir/arsse"
cp -r lib locale sql vendor www CHANGELOG UPGRADING README.md arsse.php "$pkgdir/usr/share/webapps/arsse"
cp -r manual/* "$pkgdir/usr/share/doc/arsse"
cp LICENSE AUTHORS "$pkgdir/usr/share/licenses/arsse"
cp dist/sysuser.conf "$pkgdir/usr/lib/sysusers.d/arsse.conf"
cp dist/tmpfiles.conf "$pkgdir/usr/lib/tmpfiles.d/arsse.conf"
cp dist/php-fpm.conf "$pkgdir/etc/php/php-fpm.d/arsse.conf"
cp -r dist/man "$pkgdir/usr/share"
cp -r dist/nginx dist/apache config.defaults.php "$pkgdir/etc/webapps/arsse"
# copy files requiring special permissions
install -Dm640 dist/config.php "$pkgdir/etc/webapps/arsse"
# patch generic configuration files to use Arch-specific paths and identifiers
sed -i -se 's/\/\(etc\|usr\/share\)\/arsse\//\/\1\/webapps\/arsse\//g' "$pkgdir/etc/webapps/arsse/nginx/"* "$pkgdir/etc/webapps/arsse/apache/"* "$pkgdir/usr/lib/tmpfiles.d/arsse.conf"
sed -i -se 's/\/var\/run\/php\//\/run\/php-fpm\//g' "$pkgdir/etc/webapps/arsse/nginx/"* "$pkgdir/etc/webapps/arsse/apache/"* "$pkgdir/etc/php/php-fpm.d/arsse.conf"
sed -i -se 's/www-data/http/g' "$pkgdir/etc/php/php-fpm.d/arsse.conf"
# make a duplicate PHP-FPM pool for php-legacy
sed -se 's/php-fpm/php-fpm-legacy/' "$pkgdir/etc/php/php-fpm.d/arsse.conf" > "$pkgdir/etc/php-legacy/php-fpm.d/arsse.conf"
# copy Arch-specific versions of files
install -Dm755 dist/arch/arsse "$pkgdir/usr/bin/arsse"
cp dist/arch/nginx-arsse-fcgi.conf "$pkgdir/etc/webapps/arsse/nginx/arsse-fcgi.conf"
cp dist/arch/apache-arsse-fcgi.conf "$pkgdir/etc/webapps/arsse/apache/arsse-fcgi.conf"
cp dist/arch/*.service "$pkgdir/usr/lib/systemd/system"
cp dist/arch/systemd-environment "$pkgdir/etc/webapps/arsse/systemd-environment"
}

6
dist/arch/apache-arsse-fcgi.conf vendored Normal file
View file

@ -0,0 +1,6 @@
ProxyPreserveHost On
ProxyFCGISetEnvIf "true" SCRIPT_FILENAME "/usr/share/webapps/arsse/arsse.php"
ProxyFCGISetEnvIf "-n req('Authorization')" HTTP_AUTHORIZATION "%{req:Authorization}"
# Modify the below line to begin with "unix:/run/php-fpm-legacy/" if using the php-legacy package
ProxyPass "unix:/run/php-fpm/arsse.sock|fcgi://localhost/usr/share/webapps/arsse/"

28
dist/arch/arsse vendored Normal file
View file

@ -0,0 +1,28 @@
#!/usr/bin/env bash
readonly default_php="/usr/bin/php"
php=""
check_sudo() {
if ! command -v sudo > /dev/null; then
printf "The sudo command is not available.\n"
exit 1
fi
}
# allow overriding the php executable
if [ -n "${ARSSE_PHP}" ] && command -v "${ARSSE_PHP}" > /dev/null; then
php="${ARSSE_PHP}"
else
php="${default_php}"
fi
if [ "$(whoami)" = "arsse" ]; then
"$php" /usr/share/webapps/arsse/arsse "$@"
elif [ "${UID}" -eq 0 ]; then
runuser -u "arsse" -- "$php" /usr/share/webapps/arsse/arsse "$@"
else
check_sudo
sudo -u "arsse" "$php" /usr/share/webapps/arsse/arsse "$@"
fi

37
dist/arch/arsse-fetch.service vendored Normal file
View file

@ -0,0 +1,37 @@
[Unit]
Description=The Arsse newsfeed fetching service
Documentation=https://thearsse.com/manual/
PartOf=arsse.service
[Install]
WantedBy=multi-user.target
[Service]
User=arsse
Group=arsse
Type=simple
WorkingDirectory=/usr/share/webapps/arsse
EnvironmentFile=/etc/webapps/arsse/systemd-environment
ExecStart=/usr/bin/arsse daemon
ProtectProc=invisible
NoNewPrivileges=true
ProtectSystem=full
ProtectHome=true
StateDirectory=arsse
ConfigurationDirectory=arsse
PrivateTmp=true
PrivateDevices=true
RestrictSUIDSGID=true
StandardOutput=journal
StandardError=journal
SyslogIdentifier=arsse
Restart=on-failure
RestartPreventStatus=
# These directives can be used for extra security, but are disabled for now for compatibility
#ReadOnlyPaths=/
#ReadWriePaths=/var/lib/arsse
#NoExecPaths=/
#ExecPaths=/usr/bin/php

13
dist/arch/arsse.service vendored Normal file
View file

@ -0,0 +1,13 @@
[Unit]
Description=The Arsse newsfeed management service
Documentation=https://thearsse.com/manual/
Requires=arsse-fetch.service
Wants=php-fpm.service php-legacy-fpm.service
[Install]
WantedBy=multi-user.target
[Service]
Type=oneshot
RemainAfterExit=true
ExecStart=/usr/bin/true

16
dist/arch/nginx-arsse-fcgi.conf vendored Normal file
View file

@ -0,0 +1,16 @@
fastcgi_pass_header Authorization; # required if the Arsse is to perform its own HTTP authentication
fastcgi_pass_request_body on;
fastcgi_pass_request_headers on;
fastcgi_intercept_errors off;
fastcgi_buffering off;
fastcgi_param REQUEST_METHOD $request_method;
fastcgi_param CONTENT_TYPE $content_type;
fastcgi_param CONTENT_LENGTH $content_length;
fastcgi_param REQUEST_URI $uri;
fastcgi_param QUERY_STRING $query_string;
fastcgi_param HTTPS $https if_not_empty;
fastcgi_param REMOTE_USER $remote_user;
fastcgi_param SCRIPT_FILENAME /usr/share/webapps/arsse/arsse.php;
# Modify the below line to begin with "/run/php-fpm-legacy/" if using the php-legacy package
fastcgi_pass unix:/run/php-fpm/arsse.sock;

1
dist/arch/systemd-environment vendored Normal file
View file

@ -0,0 +1 @@
ARSSE_PHP=/usr/bin/php

10
dist/arsse vendored Normal file
View file

@ -0,0 +1,10 @@
#! /usr/bin/env php
<?php
if (posix_geteuid() == 0) {
$info = posix_getpwnam("arsse");
if ($info) {
posix_setgid($info['gid']);
posix_setuid($info['uid']);
}
}
require "/usr/share/arsse/arsse.php";

15
dist/arsse.service vendored
View file

@ -1,15 +0,0 @@
[Unit]
Description=The Arsse feed fetching service
After=network.target
[Service]
User=www-data
Group=www-data
WorkingDirectory=/usr/share/arsse
Type=simple
StandardOutput=null
StandardError=syslog
ExecStart=/usr/bin/env php /usr/share/arsse/arsse.php daemon
[Install]
WantedBy=multi-user.target

8
dist/config.php vendored Normal file
View file

@ -0,0 +1,8 @@
<?php
# Please refer to config.defaults.php or the manual at /usr/share/doc/arsse/
# for possible configuration parameters
return [
'dbSQLite3File' => "/var/lib/arsse/arsse.db",
];

16
dist/debian/arsse.config vendored Normal file
View file

@ -0,0 +1,16 @@
#!/bin/sh
set -e
. /usr/share/debconf/confmodule
# Set up dbconfig-common
if test -f /usr/share/dbconfig-common/dpkg/config; then
. /usr/share/dbconfig-common/dpkg/config
dbc_dbtypes="sqlite3, pgsql, mysql"
dbc_authmethod_user="password"
dbc_go arsse "$@"
fi
# Prompt for dbconfig-common configuration
db_go || true

1
dist/debian/arsse.dirs vendored Normal file
View file

@ -0,0 +1 @@
var/lib/arsse

18
dist/debian/arsse.install vendored Normal file
View file

@ -0,0 +1,18 @@
lib usr/share/arsse/
locale usr/share/arsse/
sql usr/share/arsse/
vendor usr/share/arsse/
www usr/share/arsse/
CHANGELOG usr/share/arsse/
UPGRADING usr/share/arsse/
README.md usr/share/arsse/
arsse.php usr/share/arsse/
config.defaults.php etc/arsse/
manual usr/share/doc/arsse/
dist/man/* usr/share/man/
dist/debian/config.php etc/arsse/
dist/debian/dbconfig-common.php usr/share/arsse/
debian/bin/arsse usr/bin/
debian/nginx etc/arsse/
debian/apache etc/arsse/

1
dist/debian/arsse.links vendored Normal file
View file

@ -0,0 +1 @@
etc/arsse/config.php usr/share/arsse/config.php

29
dist/debian/arsse.postinst vendored Normal file
View file

@ -0,0 +1,29 @@
#!/bin/sh
set -e
. /usr/share/debconf/confmodule
if [ "$1" = "configure" ]; then
# Set permissions on configuration file
dpkg-statoverride --list "/etc/arsse/config.php" >/dev/null || dpkg-statoverride --update --add root www-data 640 "/etc/arsse/config.php"
# Set up dbconfig-common
if test -f /usr/share/dbconfig-common/dpkg/postinst; then
. /usr/share/dbconfig-common/dpkg/postinst
dbc_generate_include_owner="root:www-data"
dbc_generate_include_perms="0640"
dbc_generate_include="php:/var/lib/arsse/dbconfig.inc"
dbc_pgsql_createdb_encoding="UTF8' lc_collate='C"
dbc_mysql_createdb_encoding="UTF8"
dbc_dbfile_owner="root:www-data"
dbc_dbfile_perms="0660"
dbc_go arsse "$@"
fi
fi
# dh_installdeb will replace this with shell code automatically
# generated by other debhelper scripts.
#DEBHELPER#
exit 0

20
dist/debian/arsse.postrm vendored Normal file
View file

@ -0,0 +1,20 @@
#!/bin/sh
set -e
if test -f /usr/share/debconf/confmodule; then
. /usr/share/debconf/confmodule
fi
# Set up dbconfig-common
if test -f /usr/share/dbconfig-common/dpkg/postrm; then
. /usr/share/dbconfig-common/dpkg/postrm
dbc_go arsse "$@"
fi
# dh_installdeb will replace this with shell code automatically
# generated by other debhelper scripts.
#DEBHELPER#
exit 0

16
dist/debian/arsse.prerm vendored Normal file
View file

@ -0,0 +1,16 @@
#!/bin/sh
set -e
. /usr/share/debconf/confmodule
# Set up dbconfig-common
. /usr/share/dbconfig-common/dpkg/prerm
dbc_go arsse "$@"
# dh_installdeb will replace this with shell code automatically
# generated by other debhelper scripts.
#DEBHELPER#
exit 0

1
dist/debian/compat vendored Normal file
View file

@ -0,0 +1 @@
10

15
dist/debian/config.php vendored Normal file
View file

@ -0,0 +1,15 @@
<?php
/***
Please refer to config.defaults.php or the manual at /usr/share/doc/arsse/
for possible configuration parameters.
The last line includes database auto-configuration information which
Debian may have created during installation; any database-related
configuration defined in this file will override anything defined in the
included file.
***/
return [
'dbAutoUpdate' => true,
]
+ (@include "/usr/share/arsse/dbconfig-common.php");

32
dist/debian/control vendored Normal file
View file

@ -0,0 +1,32 @@
Source: arsse
Maintainer: J. King <jking@jkingweb.ca>
Section: contrib/net
Priority: optional
Standards-Version: 4.5.1
Homepage: https://thearsse.com/
Vcs-Browser: https://code.mensbeam.com/MensBeam/arsse/
Vcs-Git: https://code.mensbeam.com/MensBeam/arsse/
Build-Depends: debhelper
Package: arsse
Architecture: all
Section: contrib/net
Priority: optional
Homepage: https://thearsse.com/
Description: Multi-protocol RSS/Atom newsfeed synchronization server
The Arsse bridges the gap between multiple existing newsfeed aggregator
client protocols such as Tiny Tiny RSS, Nextcloud News and Miniflux,
allowing you to use compatible clients for many protocols with a single
server.
Depends: ${misc:Depends},
dbconfig-sqlite3 | dbconfig-pgsql | dbconfig-mysql | dbconfig-no-thanks,
php (>= 7.3.0),
php-cli,
php-intl,
php-json,
php-xml,
php-sqlite3 | php-pgsql | php-mysql
Recommends: nginx | apache2,
php-fpm,
php-curl,
ca-certificates

34
dist/debian/copyright vendored Normal file
View file

@ -0,0 +1,34 @@
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: arsse
Upstream-Contact: J. King <jking@jkingweb.ca>
Source: https://code.mensbeam.com/MensBeam/arsse/
License: Expat
Files: *
Copyright: 2017 J. King <jking@jkingweb.ca>
2017 Dustin Wilson <dustin@dustinwilson.com>
License: Expat
License: Expat
Copyright (c) 2017 J. King, Dustin Wilson
.
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
.
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

44
dist/debian/dbconfig-common.php vendored Normal file
View file

@ -0,0 +1,44 @@
<?php
# This script transforms Debian's dbconfig-common PHP-format include files
# into a form usable by The Arsse. This is necessary because The Arsse
# supports defining configuration parameters for all supported database types
# at once, using separate keys for the different types
$dbconfpath = "/var/lib/arsse/dbconfig.inc"; // path defined in postinst script
if (file_exists($dbconfpath)) {
require_once "/var/lib/arsse/dbconfig.inc";
$dbtype = $dbtype ?? "";
// the returned configuration depends on the $dbtype
if ($dbtype === "sqlite3") {
$conf = ['dbDriver' => "sqlite3"];
if (strlen((string) $basepath) && strlen((string) $dbname)) {
$conf['dbSQLite3File'] = "$basepath/$dbname";
}
} elseif ($dbtype === "pgsql") {
$conf = [
'dbDriver' => "postgresql",
'dbPostgreSQLHost' => $dbserver ?? "",
'dbPostgreSQLUser' => $dbuser ?? "arsse",
'dbPostgreSQLPass' => $dbpass ?? "",
'dbPostgreSQLPort' => (int) $dbport ?: 5432,
'dbPostgreSQLDb' => $dbname ?? "arsse",
];
} elseif ($dbtype === "mysql") {
$conf = [
'dbDriver' => "mysql",
'dbMySQLHost' => $dbserver ?? "",
'dbMySQLUser' => $dbuser ?? "arsse",
'dbMySQLPass' => $dbpass ?? "",
'dbMySQLPort' => (int) $dbport ?: 3306,
'dbMySQLDb' => $dbname ?? "arsse",
];
} else {
throw new \Exception("Debian dbconfig-common configuration file $dbconfpath is invalid");
}
return $conf;
} else {
// if no configuration file exists simply return an empty array
return [];
}

6
dist/debian/lintian-overrides vendored Normal file
View file

@ -0,0 +1,6 @@
# We make reference to "Tiny Tiny RSS"
spelling-error-in-description Tiny Tiny (duplicate word) Tiny
# The manual for DrUUID (a dependency) includes a harmless "up" link
privacy-breach-generic usr/share/arsse/vendor/jkingweb/druuid/documentation/manual.html [<link rel="up" href="http://jkingweb.ca/code/">] (http://jkingweb.ca/code/)
# We only ask dbconfig-common questions, which don't seem to require templates
no-debconf-templates

40
dist/debian/pbuilder.sh vendored Executable file
View file

@ -0,0 +1,40 @@
#! /bin/bash -e
###
# This script is fed to pbuilder to build Debian packages. The base tarball
# should be created with a command similar to the following:
#
# sudo pbuilder create --basetgz pbuilder-arsse.tgz --mirror http://ftp.ca.debian.org/debian/ --extrapackages "debhelper devscripts lintian"
#
# Thereafter pbuilder can be used to build packages with this command:
#
# sudo pbuilder execute --basetgz pbuilder-arsse.tgz --bindmounts `basedir "/path/to/release/tarball"` -- pbuilder.sh "/path/to/release/tarball"
#
# This somewhat roundabout procedure is used because the pbuilder debuild
# command does not seem to work in Arch Linux, nor does pdebuild. Doing
# as much as possible within the chroot itself works around these problems.
###
# create a temporary directory
tmp=`mktemp -d`
# define various variables
here=`dirname "$1"`
tarball=`basename "$1"`
version=`echo "$tarball" | grep -oP '\d+(?:\.\d+)*' | head -1`
out="$here/debian"
in="$tmp/arsse-$version"
# create necessary directories
mkdir -p "$in" "$out"
# extract the release tarball
tar -C "$in" -xf "$1" --strip-components=1
# repackage the release tarball into a Debian "orig" tarball
tar -C "$tmp" -czf "$tmp/arsse_$version.orig.tar.gz" "arsse-$version"
# copy the "dist/debian" directory down the tree where Debian expects it
cp -r "$in/dist/debian" "$in/debian"
# build the package
cd "$in"
debuild -us -uc
# move the resultant files to their final destination
find "$tmp" -maxdepth 1 -type f -exec mv '{}' "$out" \;

26
dist/debian/rules vendored Executable file
View file

@ -0,0 +1,26 @@
#!/usr/bin/make -f
DH_VERBOSE = 1
%:
dh $@
execute_before_dh_install:
# Adapt the systemd service for Debian: this involves using only the "arsse-fetch" unit (renamed to "arsse"), removing the "PartOf" directive, and changing the user and group to "www-data"
cp dist/systemd/arsse-fetch.service debian/arsse.service
sed -i -se 's/^PartOf=.*//' debian/arsse.service
sed -i -se 's/^\(User\|Group\)=.*/\1=www-data/' debian/arsse.service
# Adapt the init script for Debian: this involves changing the user and group to "www-data"
cp dist/init.sh debian/arsse.init
sed -i -se 's/^\([ \t]*chown\) arsse:arsse /\1 www-data:www-data /' debian/arsse.init
# Change the user and group references in tmpfiles
cp dist/tmpfiles.conf debian/arsse.tmpfiles
sed -i -se 's/ arsse / www-data /' debian/arsse.tmpfiles
sed -i -se 's/ arsse / www-data /' debian/arsse.tmpfiles
# Change the user reference in the executable file
mkdir -p debian/bin
cp dist/arsse debian/bin/arsse
sed -i -se 's/posix_getpwnam("arsse"/posix_getpwnam("www-data"/' debian/bin/arsse
# Change PHP-FPM socket paths
cp -r dist/apache dist/nginx debian
sed -i -se 's/arsse\.sock/php-fpm.sock/' debian/apache/arsse.conf debian/nginx/arsse.conf

1
dist/debian/source/format vendored Normal file
View file

@ -0,0 +1 @@
3.0 (quilt)

2
dist/debian/source/lintian-overrides vendored Normal file
View file

@ -0,0 +1,2 @@
# Development environment is slightly out of date
newer-standards-version

78
dist/init.sh vendored Normal file
View file

@ -0,0 +1,78 @@
#!/bin/sh
### BEGIN INIT INFO
# Provides: arsse
# Required-Start: $local_fs $network
# Required-Stop: $local_fs postgresql mysql
# Should-Start: postgresql mysql
# Should-Stop: postgresql mysql
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: The Advanced RSS Environment
# Description: The Arsse is a multi-protocol Web newsfeed synchronization service
### END INIT INFO
# This script is designed for Debian; some adaptation will be required for other systems
PATH=/usr/sbin/:/usr/bin:/sbin:/bin
NAME=arsse
DESC="newsfeed synchronization server"
PIDFILE=/run/arsse.pid
DAEMON=/usr/bin/$NAME
. /lib/init/vars.sh
. /lib/lsb/init-functions
arsse_start() {
touch "$PIDFILE"
chown arsse:arsse "$PIDFILE"
$DAEMON daemon --fork="$PIDFILE" || return 2
}
arsse_stop() {
killproc -p "$PIDFILE" "$DAEMON"
}
arsse_reload() {
killproc -p "$PIDFILE" "$DAEMON" HUP
}
case "$1" in
start)
log_daemon_msg "Starting $DESC" "$NAME"
if pidofproc -p $PIDFILE "$DAEMON" > /dev/null 2>&1 ; then
return 1
fi
arsse_start
;;
stop)
log_daemon_msg "Stopping $DESC" "$NAME"
arsse_stop
;;
restart)
log_daemon_msg "Restarting $DESC" "$NAME"
if pidofproc -p $PIDFILE "$DAEMON" > /dev/null 2>&1 ; then
arsse_stop
fi
arsse_start
;;
try-restart)
if pidofproc -p $PIDFILE "$DAEMON" > /dev/null 2>&1 ; then
log_daemon_msg "Restarting $DESC" "$NAME"
arsse_stop
arsse_start
fi
;;
reload|force-reload)
log_daemon_msg "Reloading $DESC" "$NAME"
arsse_reload
;;
status)
status_of_proc -p $PIDFILE $DAEMON $NAME
exit $?
;;
*)
echo "Usage: $0 {start|stop|restart|try-restart|reload|status}" >&2
exit 3
;;
esac

342
dist/man/man1/arsse.1 vendored Normal file
View file

@ -0,0 +1,342 @@
.Dd October 27, 2023
.Dt ARSSE 1
.Os "The Arsse" 0.10.4
.
.
.Sh NAME
.Nm arsse
.Nd manage an instance of The Advanced RSS Environment (The Arsse)
.
.
.Sh SYNOPSIS
.Nm "arsse user"
.Op Nm list
.Nm "arsse user add"
.Ar username
.Op Ar password
.Op Fl Fl admin
.Nm "arsse user remove"
.Ar username
.Nm "arsse user show"
.Ar username
.Nm "arsse user set"
.Ar username
.Ar property
.Ar value
.Nm "arsse user unset"
.Ar username
.Ar property
.Nm "arsse user set\-pass"
.Ar username
.Op Ar password
.Op Fl Fl fever
.Nm "arsse user unset\-pass"
.Ar username
.Op Fl Fl fever
.Nm "arsse user auth"
.Ar username
.Op Ar password
.Op Fl Fl fever
.Nm "arsse token list"
.Ar username
.Nm "arsse token create"
.Ar username
.Op Ar label
.Nm "arsse token revoke"
.Ar username
.Op Ar token
.Nm "arsse import"
.Ar username
.Op Ar file
.Op Fl f | Fl Fl flat
.Op Fl r | Fl Fl replace
.Nm "arsse export"
.Ar username
.Op Ar file
.Op Fl f | Fl Fl flat
.Nm "arsse daemon"
.Op Fl Fl fork Ns = Ns Ar pidfile
.Nm "arsse feed refresh\-all"
.Nm "arsse feed refresh"
.Ar n
.Nm "arsse conf save\-defaults"
.Nm "arsse"
.Fl Fl version | Fl h | Fl Fl help
.
.
.Sh DESCRIPTION
.Nm
allows a sufficiently privileged user to perform various administrative operations related to The Arsse, including:
.Pp
.Bl -bullet -compact
.It
Adding and removing users and managing their metadata
.It
Managing passwords and authentication tokens
.It
Importing and exporting OPML newsfeed-lists
.El
.Pp
These are documented in the next section
.Sx COMMANDS Ns No .
Further, seldom-used commands are documented in the subsequent section
.Sx ADDITIONAL COMMANDS Ns No .
.
.
.Sh COMMANDS
.
.Ss Managing users and metadata
.Bl -tag
.It Nm "user" Op Nm list
Displays a simple list of user names with one entry per line
.It Nm "user add" Ar username Oo Ar password Oc Oo Fl Fl admin Oc
Adds a new user to the database with the specified username and password.
If
.Ar password
is omitted a random password will be generated and printed.
.Pp
The
.Fl Fl admin
flag may be used to mark the user as an administrator.
This has no meaning within the context of The Arsse as a whole,
but it is used control access to certain features in the Miniflux and Nextcloud News protocols.
.It Nm "user remove" Ar username
Immediately removes a user from the database.
All associated data (folders, subscriptions, etc.) are also removed.
.It Nm "user show" Ar username
Displays a table of metadata properties and their assigned values for
.Ar username Ns No .
These properties are primarily used by the Miniflux protocol.
Consult the section
.Sx USER METADATA
for details.
.It Nm "user set" Ar username Ar property Ar value
Sets a metadata property for a user.
These properties are primarily used by the Miniflux protocol.
Consult the section
.Sx USER METADATA
for details.
.It Nm "user unset" Ar username Ar property
Clears a metadata property for a user.
The property is thereafter set to its default value, which is protocol-dependent.
.El
.
.Ss Managing passwords and authentication tokens
.Bl -tag
.It Nm "user set\-pass" Ar username Oo Ar password Oc Oo Fl Fl fever Oc
Changes a user's password to the specified value.
If no password is specified, a random password will be generated and printed.
.Pp
The
.Fl Fl fever
option sets a user's Fever protocol password instead of their general password.
As the Fever protocol requires that passwords be stored insecurely,
users do not have Fever passwords by default, and logging in to the Fever protocol is disabled until a suitable password is set.
It is highly recommended that a user's Fever password be different from their general password.
.It Nm "user unset\-pass" Ar username Oo Fl Fl fever Oc
Unsets a user's password, effectively disabling their account.
As with password setting, the
.Fl Fl fever
option may be used to operate on a user's Fever password instead of their general password.
.It Nm "user auth" Ar username Ar password Oo Fl Fl fever Oc
Tests logging a user in.
This only checks that the user's password is correctly recognized;
it has no side effects.
.Pp
The
.Fl Fl fever
option may be used to test the user's Fever protocol password, if any.
.It Nm "token list" Ar username
Displays a user's authentication tokens in a simple tabular format.
These tokens act as an alternative means of authentication for the Miniflux protocol and may be required by some clients.
They do not expire.
.It Nm "token create" Ar username Oo Ar label Oc
Creates a new random login token and prints it.
These tokens act as an alternative means of authentication for the Miniflux protocol and may be required by some clients.
An optional
.Ar label
may be specified to give the token a meaningful name.
.It Nm "token revoke" Ar username Oo Ar token Oc
Deletes the specified
.Ar token
from the database.
The token itself must be supplied, not its label.
If it is omitted all tokens for
.Ar username
are revoked.
.El
.
.Ss Importing and exporting data
.Bl -tag
.It Nm "import" Ar username Oo Ar file Oc Oo Fl r | Fl Fl replace Oc Oo Fl f | Fl Fl flat Oc
Imports the newsfeeds, folders, and tags found in the OPML formatted
.Ar file
into the account of the specified user.
If no file is specified, data is instead read from standard input.
Import operations are atomic:
if any of the newsfeeds listed in the input cannot be retrieved, the entire import operation will fail.
.Pp
The
.Fl Fl replace
(or
.Fl r Ns
) option interprets the OPML file as the list of
.Em all
desired newsfeeds, folders and tags, performing any deletion or moving of existing entries which do not appear in the flle.
If this option is not specified, the file is assumed to list desired
.Em additions only Ns No .
.Pp
The
.Fl Fl flat
(or
.Fl f Ns
) option can be used to ignore any folder structures in the file, importing any newsfeeds directly into the root folder.
Combining this with the
.Fl Fl replace
option is possible.
.It Nm "export" Ar username Oo Ar file Oc Oo Fl f | Fl Fl flat Oc
Exports a user's newsfeeds, folders, and tags to the OPML file specified by
.Ar file Ns
, or standard output if no file is specified.
Note that due to a limitation of the OPML format, any commas present in tag names will not be retained in the export.
.Pp
The
.Fl Fl flat
(or
.Fl f Ns
) option can be used to omit folders from the export.
Some OPML implementations may not support folders, or arbitrary nesting;
this option may be used when planning to import into such software.
.El
.
.
.Sh ADDITIONAL COMMANDS
.Bl -tag
.It Nm "daemon" Oo Fl Fl fork Ns = Ns Ar pidfile Oc
Starts the newsfeed fetching service.
Normally this command is only invoked by systemd.
.Pp
The
.Fl Fl fork
option executes an "old-style" fork-then-terminate daemon rather than a "new-style" non-terminating daemon.
This option should only be employed if using a System V-style init daemon on POSIX systems;
normally systemd is used. When using this option the daemon will write its process identifier to
.Ar pidfile
after forking.
.It Nm "feed refresh\-all"
Performs a one-time fetch of all stale feeds.
This command can be used as the basis of a
.Nm cron
job to keep newsfeeds up-to-date.
.It Nm "feed refresh" Ar n
Performs a one-time fetch of the feed (not subscription) identified by integer
.Ar n Ns No .
This is used internally by the fetching service and should not normally be needed.
.It Nm "conf save\-defaults" Oo Ar file Oc
Prints default configuration parameters to standard output, or to
.Ar file
if specified.
Each parameter is annotated with a short description of its purpose and usage.
.El
.
.
.Sh USER METADATA
User metadata are primarily used by the Miniflux protocol,
and most properties have identical or similar names to those used by Miniflux.
Properties may also affect other protocols, or conversely may have no effect even when using the Miniflux protocol;
this is noted below when appropriate.
.Pp
Booleans accept any of the values
.Ar true Ns No / Ns Ar false Ns No ,
.Ar 1 Ns No / Ns Ar 0 Ns No ,
.Ar yes Ns No / Ns Ar no Ns No ,
or
.Ar on Ns No / Ns Ar off Ns No .
.Pp
The following metadata properties exist for each user:
.Pp
.Bl -tag
.It Cm num No (integer)
The numeric identifier of the user.
This is assigned at user creation and is read-only.
.It Cm admin No (boolean)
Boolean. Whether the user is an administrator.
Administrators may manage other users via the Miniflux protocol,
and also may trigger feed updates manually via the Nextcloud News protocol.
.It Cm lang No (string)
The preferred language of the user as a BCP 47 language tag, for example "en-ca".
Note that since The Arsse currently only includes English text it is not used by The Arsse itself,
but clients may use this metadatum in protocols which expose it.
.It Cm tz No (string)
The time zone of the user as a Time Zone Database identifier, for example "America/Los_Angeles".
.It Cm root_folder_name No (string)
The name of the root folder, in protocols which allow it to be renamed.
.It Cm sort_asc No (boolean)
Whether the user prefers ascending sort order for articles.
Descending order is usually the default,
but explicitly setting this property false will also make a preference for descending order explicit.
.It Cm theme No (string)
The user's preferred user-interface theme.
This is not used by The Arsse itself, but clients may use this metadatum in protocols which expose it.
.It Cm page_size No (integer)
The user's preferred page size when listing articles.
This is not used by The Arsse itself, but clients may use this metadatum in protocols which expose it.
.It Cm shortcuts No (boolean)
Whether to enable keyboard shortcuts.
This is not used by The Arsse itself, but clients may use this metadatum in protocols which expose it.
.It Cm gestures No (boolean)
Whether to enable touch gestures.
This is not used by The Arsse itself, but clients may use this metadatum in protocols which expose it.
.It Cm reading_time No (boolean)
Whether to calculate and display the estimated reading time for articles.
Currently The Arsse does not calculate reading time, so changing this will likely have no effect.
.It Cm stylesheet No (string)
A user stylesheet in CSS format.
This is not used by The Arsse itself, but clients may use this metadatum in protocols which expose it.
.El
.
.
.Sh EXAMPLES
.Bl -tag
.It Add an administrator to the database with an explicit password:
.Bd -literal
$ arsse user add \-\-admin alice "Curiouser and curiouser!"
.Ed
.It Add a regular user to the database with a random password:
.Bd -literal
$ arsse user add "Bob the Builder"
bLS!$_UUZ!iN2i_!^IC6
.Ed
.It Make Bob the Builder an administrator:
.Bd -literal
$ arsse user set "Bob the Builder" admin true
.Ed
.It Disable Alice's account by clearing her password:
.Bd -literal
$ arsse user unset\-pass alice
.Ed
.It Move all of Foobar's newsfeeds to the root folder:
.Bd -literal
$ arsse export foobar \-f | arsse import \-r foobar
.Ed
.It Fail to log in as Alice:
.Bd -literal
$ arsse user auth alice "Oh, dear!"
Authentication failed
$ echo $?
1
.Ed
.El
.
.
.Sh REPORTING BUGS
Any bugs found in The Arsse may be reported on the Web via the
.Lk https://code.mensbeam.com/MensBeam/arsse "MensBeam code repository"
or may be directed to the principal authors by e-mail:
.Pp
.Bl -bullet -compact
.It
.Lk https://jkingweb.ca/ "J. King"
.It
.Lk https://dustinwilson.com/ "Dustin Wilson"
.El

51
dist/nginx.conf vendored
View file

@ -1,51 +0,0 @@
server {
server_name news.example.com;
listen 80; # adding HTTPS configuration is highly recommended
# redirect to HTTPS, if desired
#if ($https != "on") {rewrite ^ https://$host$request_uri;}
# the userPreAuth setting should be enabled if the Web server is handling authentication
#auth_basic "Advanced RSS Environment";
root /usr/share/arsse/www;
index index.html;
location / {
try_files $uri $uri/ =404;
}
location @arsse_auth {
# the userPreAuth setting should be enabled if the Web server is handling authentication
#auth_basic "Advanced RSS Environment";
include /usr/share/arsse/dist/nginx-fcgi.conf;
}
location @arsse_no_auth {
auth_basic off;
include /usr/share/arsse/dist/nginx-fcgi.conf;
}
# NextCloud News protocol
location /index.php/apps/news/api {
try_files $uri @arsse_auth;
location ~ ^/index\.php/apps/news/api/?$ {
try_files $uri @arsse_no_auth;
}
}
# Tiny Tiny RSS protocol
location /tt-rss/api {
try_files $uri @arsse_no_auth;
}
# Tiny Tiny RSS feed icons
location /tt-rss/feed-icons/ {
try_files $uri @arsse_no_auth;
}
# Tiny Tiny RSS special-feed icons
location /tt-rss/images/ {
auth_basic off;
root /usr/share/arsse/www;
try_files $uri =404;
}
}

View file

@ -1,13 +1,15 @@
fastcgi_pass php; # PHP is assumed to already be configured for FastCGI operation
fastcgi_pass_header Authorization; # required if the Arsse is to perform its own HTTP authentication
fastcgi_pass_request_body on;
fastcgi_pass_request_headers on;
fastcgi_intercept_errors off;
fastcgi_buffering off;
fastcgi_param SCRIPT_FILENAME /usr/share/arsse/arsse.php;
fastcgi_param REQUEST_METHOD $request_method;
fastcgi_param CONTENT_TYPE $content_type;
fastcgi_param CONTENT_LENGTH $content_length;
fastcgi_param REQUEST_URI $request_uri;
fastcgi_param REQUEST_URI $uri;
fastcgi_param QUERY_STRING $query_string;
fastcgi_param HTTPS $https if_not_empty;
fastcgi_param REMOTE_USER $remote_user;
fastcgi_param REMOTE_USER $remote_user;
fastcgi_pass unix:/var/run/php/arsse.sock;
fastcgi_param SCRIPT_FILENAME /usr/share/arsse/arsse.php;

49
dist/nginx/arsse-loc.conf vendored Normal file
View file

@ -0,0 +1,49 @@
# Any provided static files
location / {
try_files $uri $uri/ =404;
}
# Nextcloud News protocol
location /index.php/apps/news/api {
try_files $uri @arsse;
location ~ ^/index\.php/apps/news/api/?$ {
try_files $uri @arsse_public;
}
}
# Tiny Tiny RSS protocol
location /tt-rss/api {
try_files $uri @arsse;
}
# Tiny Tiny RSS feed icons
location /tt-rss/feed-icons/ {
try_files $uri @arsse;
}
# Tiny Tiny RSS special-feed icons; these are static files
location /tt-rss/images/ {
try_files $uri =404;
}
# Fever protocol
location /fever/ {
try_files $uri @arsse;
}
# Miniflux protocol
location /v1/ {
# If put behind HTTP authentication token login will not be possible
try_files $uri @arsse;
}
# Miniflux version number
location /version {
try_files $uri @arsse_public;
}
# Miniflux "health check"
location /healthcheck {
try_files $uri @arsse_public;
}

13
dist/nginx/arsse.conf vendored Normal file
View file

@ -0,0 +1,13 @@
root /usr/share/arsse/www;
location @arsse {
# HTTP authentication may be enabled for this location, though this may impact some features
include /etc/arsse/nginx/arsse-fcgi.conf;
}
location @arsse_public {
# HTTP authentication should not be enabled for this location
include /etc/arsse/nginx/arsse-fcgi.conf;
}
include /etc/arsse/nginx/arsse-loc.conf;

13
dist/nginx/example.conf vendored Normal file
View file

@ -0,0 +1,13 @@
server {
server_name news.example.com;
listen 80;
listen [::]:80;
listen 443 ssl http2;
listen [::]:443 ssl http2;
ssl_certificate /etc/letsencrypt/live/news.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/news.example.com/privkey.pem;
ssl_trusted_certificate /etc/letsencrypt/live/news.example.com/chain.pem;
include /etc/arsse/nginx/arsse.conf;
}

11
dist/php-fpm.conf vendored Normal file
View file

@ -0,0 +1,11 @@
[arsse]
user = arsse
group = arsse
listen = /var/run/php/arsse.sock
listen.owner = arsse
listen.group = www-data
pm = dynamic
pm.max_children = 5
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3

36
dist/systemd/arsse-fetch.service vendored Normal file
View file

@ -0,0 +1,36 @@
[Unit]
Description=The Arsse newsfeed fetching service
Documentation=https://thearsse.com/manual/
PartOf=arsse.service
[Install]
WantedBy=multi-user.target
[Service]
User=arsse
Group=arsse
Type=simple
WorkingDirectory=/usr/share/arsse
ExecStart=/usr/bin/arsse daemon
ProtectProc=invisible
NoNewPrivileges=true
ProtectSystem=full
ProtectHome=true
StateDirectory=arsse
ConfigurationDirectory=arsse
PrivateTmp=true
PrivateDevices=true
RestrictSUIDSGID=true
StandardOutput=journal
StandardError=journal
SyslogIdentifier=arsse
Restart=on-failure
RestartPreventStatus=
# These directives can be used for extra security, but are disabled for now for compatibility
#ReadOnlyPaths=/
#ReadWriePaths=/var/lib/arsse
#NoExecPaths=/
#ExecPaths=/usr/bin/php

14
dist/systemd/arsse.service vendored Normal file
View file

@ -0,0 +1,14 @@
[Unit]
Description=The Arsse newsfeed management service
Documentation=https://thearsse.com/manual/
Requires=arsse-fetch.service
BindsTo=php-fpm.service
After=php-fpm.service
[Install]
WantedBy=multi-user.target
[Service]
Type=oneshot
RemainAfterExit=true
ExecStart=/usr/bin/true

1
dist/sysuser.conf vendored Normal file
View file

@ -0,0 +1 @@
u arsse - "The Arsse" /var/lib/arsse -

5
dist/tmpfiles.conf vendored Normal file
View file

@ -0,0 +1,5 @@
z /usr/bin/arsse 0755 root arsse - -
z /etc/arsse/config.php 0640 root arsse - -
L /usr/share/arsse/config.php - root arsse - /etc/arsse/config.php
d /var/lib/arsse 0750 arsse arsse - -
L /usr/share/arsse/arsse - root arsse - /usr/share/arsse/arsse.php

15
docs/config.json Normal file
View file

@ -0,0 +1,15 @@
{
"title": "The Advanced RSS Environment",
"tagline": "The clean & modern RSS server that doesn't give you any crap.",
"author": "J. King",
"languages": {
"en": "English"
},
"themes_directory": "docs/theme",
"html": {
"theme": "arsse",
"float": false,
"toggle_code": false,
"search": false
}
}

15
docs/en/010_About.md Normal file
View file

@ -0,0 +1,15 @@
The Advanced RSS Environment (affectionately called "The Arsse") is a news aggregator server which implements multiple synchronization protocols. Unlike most other aggregator servers, The Arsse does not include a Web front-end (though one is planned as a separate project), and it relies on [existing protocols](Supported_Protocols) to maximize compatibility with [existing clients](Compatible_Clients). Supported protocols are:
- Miniflux
- Nextcloud News
- Tiny Tiny RSS
- Fever
The primary goal of The Arsse is to bridge the many isolated ecosystems of client software for the various news synchronization protocols currently in existence. We want people to be able to use the best client software for whatever operating system they use, regardless of the protocols the client supports.
Though The Arsse currently supports only a few protocols, many more are within scope for inclusion, as long as the protocol is not specific to a single service and has clients available.
At present the software should be considered in an "alpha" state: many features one would expect from other similar software have yet to be implemented. Areas of future work include:
- Providing more sync protocols (Google Reader, others)
- Better packaging and configuration samples

View file

@ -0,0 +1,71 @@
[TOC]
# Downloading The Arsse
Since version 0.9.2 The Arsse is available from the [Arch User Repository](https://aur.archlinux.org/) as packages `arsse` and `arsse-git`. The latter should normally only be used to test bug fixes.
Generic release tarballs may also be downloaded [from our Web site](https://thearsse.com), and the `PKGBUILD` file (found under `arsse/dist/arch/`) can then be extracted alongside the tarball and used to build the `arsse` package. Installing directly from the generic release tarball without producing an Arch package is not recommended as the package-building process performs various adjustments to handle Arch peculiarities.
# Installation
For illustrative purposes, this document assumes the `yay` [AUR helper](https://wiki.archlinux.org/title/AUR_helpers) will be used to download, build, and install The Arsse. This section summarises the steps necessary to configure and use The Arsse after installtion:
```sh
# Install the package
sudo yay -S arsse
# Enable the necessary PHP extensions; curl is optional but recommended; pdo_sqlite may be used instead of sqlite3, but this is not recommended
sudo sed -i -e 's/^;\(extension=\(curl\|iconv\|intl\|sqlite3\)\)$/\1/' /etc/php/php.ini
# Enable and start the necessary systemd units
sudo systemctl enable php-fpm arsse
sudo systemctl restart php-fpm arsse
```
Note that the above is the most concise process, not necessarily the recommended one. In particular [it is recommended](https://wiki.archlinux.org/title/PHP#Extensions) to use `/etc/php/conf.d/` to enable PHP extensions rather than editing `php.ini` as done above.
# Web server configuration
Sample configuration for both Nginx and Apache HTTP Server can be found in `/etc/webapps/arsse/nginx/` and `/etc/webapps/arsse/apache/`, respectively. The `example.conf` files are basic virtual host examples; the other files they include should normally be usable without modification, but may be modified if desired.
If using Apache HTTP Server the `mod_proxy` and `mod_proxy_fcgi` modules must be enabled. This can be achieved by adding the following lines to your virtual host or global configuration:
```apache
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_fcgi_module modules/mod_proxy_fcgi.so
```
No additional set-up is required for Nginx.
# Using an alternative PHP interpreter
The above instructions assume you will be using the `php` package as your PHP interpreter. If you wish to use `php-legacy` (which is always one feature version behind, for compatibility) a few configuration tweaks are required. The follwoing commands are a short summary:
```sh
# Enable the necessary PHP extensions; curl is optional but recommended; pdo_sqlite may be used instead of sqlite3, but this is not recommended
sudo sed -i -e 's/^;\(extension=\(curl\|iconv\|intl\|sqlite3\)\)$/\1/' /etc/php-legacy/php.ini
# Modify the system service's environment
sudo sed -i -e 's/^ARSSE_PHP=.*/ARSSE_PHP=\/usr\/bin\/php-legacy/' /etc/webapps/arsse/systemd-environment
# Modify the PAM environment for the administrative CLI
echo "export ARSSE_PHP=/usr/bin/php-legacy" | sudo tee -a /etc/profile.d/arsse >/dev/null
# Modify the Nginx and Apache HTTPD configurations
sudo sed -i -se 's/\/run\/php-fpm\//\/run\/php-fpm-legacy\//' /etc/webapps/arsse/apache/arsse-fcgi.conf /etc/webapps/arsse/nginx/arsse-fcgi.conf
```
The above procedure can also be applied to use another PHP version from AUR if so desired.
# Next steps
If using a database other than SQLite, you will likely want to [set it up](/en/Getting_Started/Database_Setup) before doing anything else.
In order for The Arsse to serve users, those users [must be created](/en/Using_The_Arsse/Managing_Users).
You may also want to review the `config.defaults.php` file included in `/etc/webapps/arsse/` or consult [the documentation for the configuration file](/en/Getting_Started/Configuration), though The Arsse should function with the default configuration.
# Upgrading
Upgrading The Arsse is done like any other package. By default The Arsse will perform any required database schema upgrades when the new version is executed, so the service does need to be restarted:
```sh
sudo systemctl restart arsse
```
Occasionally changes to Web server configuration have been required, such as when new protocols become supported; these changes are always explicit in the `UPGRADING` file.

View file

@ -0,0 +1,72 @@
[TOC]
# Downloading The Arsse
Since version 0.10.0 pre-built Debian packages for The Arsse are available from the [OpenSUSE Build Service](https://build.opensuse.org/) (OBS) under [the author's personal project repository](https://build.opensuse.org/package/show/home:JKingWeb/arsse). This is the preferred method for instaling the software and is the means documented below.
Generic release tarballs may also be downloaded [from our Web site](https://thearsse.com), and a Debian package built manually. Installing directly from the generic release tarball without producing a Debian package is not recommended as the Debian packages make the set-up process on Debian systems significantly simpler.
# Adding the repository
In order to install The Arsse, the OBS repository must first be configured along with its signing key:
```sh
# Add the key
wget -q -O - "https://download.opensuse.org/repositories/home:/JKingWeb/Debian_Unstable/Release.key" | gpg --dearmor | sudo tee "/usr/share/keyrings/arsse-obs-keyring.gpg" >/dev/null
# Add the repository
echo "deb [signed-by=/usr/share/keyrings/arsse-obs-keyring.gpg] https://download.opensuse.org/repositories/home:/JKingWeb/Debian_Unstable/ ./" | sudo tee "/etc/apt/sources.list.d/arsse-obs.list" >/dev/null
# Update APT's database
sudo apt update -qq
```
Please note that the "Unstable" qualifier in the repository URL is a reference to Debian's "sid" release and is not a reflection on The Arsse's stability. The repository should be suitable for any Debian version or derivative which includes a sufficiently recent version of PHP.
# Installation
Once the OBS repository is configured, installing The Arsse is achieved with a single command:
```sh
sudo apt install arsse
```
After installation is complete The Arsse will be started automatically.
During the installation process you will be prompted whether to allow `dbconfig-common` to configure The Arsse's database automatically. The default `sqlite3` (SQLite) option is a good choice, but `pgsql` (PostgreSQL) and `mysql` (MySQL) are possible alternatives. If you wish to [use a database other than SQLite](/en/Getting_Started/Database_Setup/index), you should install it before installing The Arsse:
```sh
# Install PostgreSQL
sudo apt install postgresql php-pgsql
# Install MySQL
sudo apt install mysql-server php-mysql
# Install SQLite explicitly
sudo apt install php-sqlite3
```
If you wish to change the database backend after having installed The Arsse, running `dpkg-reconfigure` after installing the database server can be used to achieve this:
```sh
sudo dpkg-reconfigure arsse
```
# Web server configuration
Sample configuration for both Nginx and Apache HTTP Server can be found in `/etc/arsse/nginx/` and `/etc/arsse/apache/`, respectively. The `example.conf` files are basic virtual host examples; the other files they include should normally be usable without modification, but may be modified if needed or desired. In particularly users of systems older than Debian 11 (Bullseye) or Ubuntu 20.04 (Focal Fossa) or derivatives will need to change the PHP-FPM socket path in Nginx or Apache's `arsse.conf`.
In order to use Apache HTTP Server the FastCGI proxy module must be enabled and the server restarted:
```sh
sudo a2enmod proxy proxy_fcgi
sudo systemctl restart apache2
```
No additional set-up is required for Nginx.
# Next steps
In order for The Arsse to serve users, those users [must be created](/en/Using_The_Arsse/Managing_Users).
You may also want to review the `config.defaults.php` file included in the download package and create [a configuration file](/en/Getting_Started/Configuration), though The Arsse can function even without using a configuration file.
# Upgrading
Upgrading The Arsse is done like any other package. Occasionally changes to Web server configuration have been required, such as when new protocols become supported; these changes are always explicit in the `UPGRADING` file.

View file

@ -0,0 +1,53 @@
# Downloading The Arsse
The Arsse should run on any operating system for which PHP and a Web server are available, but only the combination of Linux, Systemd, Nginx, and PHP-FPM has been extensively tested.
Below are very generic instructions and suggestions for installing The Arsse on systems for which pre-built packages are not available.
# Requirements
The Arsse has the following requirements:
- A Web server such as:
- [Nginx](https://nginx.org)
- [Apache HTTP server](https://httpd.apache.org) 2.4 or later
- PHP 7.3.0 or later with the following extensions:
- [intl](https://php.net/manual/en/book.intl.php), [json](https://php.net/manual/en/book.json.php), [hash](https://php.net/manual/en/book.hash.php), [filter](https://php.net/manual/en/book.filter.php), and [dom](https://php.net/manual/en/book.dom.php)
- [simplexml](https://php.net/manual/en/book.simplexml.php), and [iconv](https://php.net/manual/en/book.iconv.php)
- One of:
- [sqlite3](https://php.net/manual/en/book.sqlite3.php) or [pdo_sqlite](https://php.net/manual/en/ref.pdo-sqlite.php) for SQLite databases
- [pgsql](https://php.net/manual/en/book.pgsql.php) or [pdo_pgsql](https://php.net/manual/en/ref.pdo-pgsql.php) for PostgreSQL 10 or later databases
- [mysqli](https://php.net/manual/en/book.mysqli.php) or [pdo_mysql](https://php.net/manual/en/ref.pdo-mysql.php) for MySQL/Percona 8.0.11 or later databases
- [curl](https://php.net/manual/en/book.curl.php) (optional)
- [posix](https://php.net/manual/en/book.posix.php) and [pcntl](https://php.net/manual/en/book.pcntl.php) (both optional)
- An interface between PHP and the Web server, such as [PHP-FPM](https://php.net/manual/en/install.fpm.php)
- Privileges either to create and run system services, or to run cron jobs
# Installation
1. Download [the latest release](https://thearsse.com/releases/current) and extract it somewhere, such as `/usr/share/arsse/`
2. [Set up your database](/en/Getting_Started/Database_Setup)
3. Create [a configuration file](/en/Getting_Started/Configuration) if needed
4. Consult the files under `dist/nginx` and `dist/apache` for sample Web server configuration
5. Consult `dist/arsse` for a sample executable script which drops privileges on POSIX systems
6. Start the newsfeed fetching service:
- Sample Systemd service files are available under `dist/systemd`
- A sample System V init script is available in `dist/init.sh`
- A persistent process can be started by running `php arsse.php daemon`
- It is also possible [to use cron](/en/Using_The_Arsse/Other_Topics.html#page_Refreshing_newsfeeds_with_a_cron_job) or a similar task-scheduling tool
7. [Create users](/en/Using_The_Arsse/Managing_Users) to grant them access
# Upgrading
Upgrading The Arsse is usually simple:
1. Download the latest release
2. Check the `UPGRADING` file for any special notes
3. Stop the newsfeed refreshing service if it is running
4. Back up your configurationm and database
5. Extract the new version on top of the old one
6. Restart the newsfeed refreshing service
By default The Arsse will perform any required database schema upgrades when the new version is executed.
Occasionally changes to Web server configuration have been required, when new protocols become supported; such changes are always explicit in the `UPGRADING` file

View file

@ -0,0 +1,16 @@
# Installing from a package manager
We currently provide a few pre-built installation packages for the following operating systems:
- [Arch Linux and derivatives](On_Arch_Linux)
- [Debian and derivatives](On_Debian_and_Derivatives)
These packages significantly simplify installation, though a bit of manual effort may still be required. Updating The Arsse using these packages should require no manual intervention.
# Installing manually
For other systems The Arsse must currently be installed manually. As each operating system is different, we can only provide very general instructions:
- [Installing on other systems](On_Other_Systems)
We hope to support more operating systems in the future.

View file

@ -0,0 +1,24 @@
# About
<dl>
<dt>Supported since</dt>
<dd>0.1.0</dd>
<dt>dbDriver identifier</dt>
<dd>sqlite3</dd>
<dt>Minimum version</dt>
<dd>3.8.3</dd>
<dt>Configuration</dt>
<dd><a href="../Configuration.html#page_Database_settings">General</a>, <a href="../Configuration.html#page_Database_settings_specific_to_SQLite_3">Specific</a></dd>
</dl>
SQLite requires very little set-up. By default the database will be created at one of the following locations depending on installation method:
| Installation method | Default database path |
|---------------------|------------------------------------------------------|
| Arch Linux package | `/var/lib/arsse/arsse.db` |
| Debian package | `/var/lib/dbconfig-common/sqlite3/arsse/arsse` |
| Manual installation | `arsse.db` in the The Arsse's installation directory |
This path can be changed with the [`dbSQLite3File` setting](/en/Getting_Started/Configuration#page_dbSQLite3File).
Regardless of the location used, The Arsse **must** be able to both read from and write to the database file, as well as create files in its directory. This is because SQLite also creates a write-ahead log file and a shared-memory file during operation.

View file

@ -0,0 +1,37 @@
# About
<dl>
<dt>Supported since</dt>
<dd>0.6.0</dd>
<dt>dbDriver identifier</dt>
<dd>postgresql</dd>
<dt>Minimum version</dt>
<dd>10</dd>
<dt>Configuration</dt>
<dd><a href="../Configuration.html#page_Database_settings">General</a>, <a href="../Configuration.html#page_Database_settings_specific_to_PostgreSQL">Specific</a></dd>
</dl>
If for whatever reason an SQLite database does not suit your configuration, PostgreSQL is the best alternative. It is functionally equivalent to SQLite in every way.
# Set-up
In order to use a PostgreSQL database for The Arsse, the database must already exist. The procedure for creating a database can differ between systems, but a typical Linux procedure is as follows:
```sh
sudo -u postgres psql -c "CREATE USER arsseuser WITH PASSWORD 'super secret password'"
sudo -u postgres psql -c "CREATE DATABASE arssedb WITH OWNER arsseuser"
```
The Arsse must then be configured to use the created database. A suitable [configuration file](/en/Getting_Started/Configuration) might look like this:
```php
<?php
return [
'dbDriver' => "postgresql",
'dbPostgreSQLUser' => "arsseuser",
'dbPostgreSQLPass' => "super secret password",
'dbPostgreSQLDb' => "arssedb",
];
```
Numerous alternate configurations are possible; the above is merely the simplest.

View file

@ -0,0 +1,42 @@
# About
<dl>
<dt>Supported since</dt>
<dd>0.6.0</dd>
<dt>dbDriver identifier</dt>
<dd>mysql</dd>
<dt>Minimum version</dt>
<dd>8.0.11</dd>
<dt>Configuration</dt>
<dd><a href="../Configuration.html#page_Database_settings">General</a>, <a href="../Configuration.html#page_Database_settings_specific_to_MySQL">Specific</a></dd>
</dl>
While MySQL can be used as a database for The Arsse, this is **not recommended** due to MySQL's technical limitations. It is fully functional, but may fail with some newsfeeds where other database systems do not. Additionally, it is particularly important before upgrading from one version of The Arsse to the next to back up your database: a failure in a database upgrade can corrupt your database much more easily than when using other database systems.
You are therefore strongly advised not to use MySQL. Though our MySQL test suite ensures functionally identical behaviour to SQLite and PostgreSQL for the supplied test data in a default MySQL configuration, there are [many other subtle ways in which it can fail](https://web.archive.org/web/20190929090114/https://grimoire.ca/mysql/choose-something-else), and we do not have the manpower to account for most of these with certainty.
Also please note that as of this writing MariaDB cannot be used in place of MySQL as it lacks features of MySQL 8 which The Arsse requires (see the [relevant MariaDB issue](https://jira.mariadb.org/browse/MDEV-18511) for details). The awkwardly-named [_Percona Server for MySQL_](https://www.percona.com/software/mysql-database/percona-server), on the other hand, will work.
# Set-up
In order to use a MySQL database for The Arsse, the database must already exist. The procedure for creating a database can differ between systems, but a typical Linux procedure is as follows:
```sh
sudo mysql -e "CREATE USER 'arsseuser'@'localhost' IDENTIFIED BY 'super secret password'"
sudo mysql -e "CREATE DATABASE arssedb"
sudo mysql -e "GRANT ALL ON arssedb.* TO 'arsseuser'@'localhost'"
```
The Arsse must then be configured to use the created database. A suitable [configuration file](/en/Getting_Started/Configuration) might look like this:
```php
<?php
return [
'dbDriver' => "mysql",
'dbMySQLUser' => "arsseuser",
'dbMySQLPass' => "super secret password",
'dbMySQLDb' => "arssedb",
];
```
Numerous alternate configurations are possible; the above is merely the simplest.

View file

@ -0,0 +1,11 @@
The Arsse supports the following database backends:
- [SQLite 3.8.3](SQLite) and later
- [PostgreSQL 10](PostgreSQL) and later
- [MySQL 8.0.11](MySQL) and later
All of the above are supported both via their PDO driver extensions as well as their native PHP extensions. One or the other is selected based on availability in your PHP installation.
Functionally there is no reason to prefer either SQLite or PostgreSQL over the other. SQLite is significantly simpler to set up in most cases, requiring only read and write access to a containing directory in order to function; PostgreSQL may perform better than SQLite when serving hundreds of users or more, though this has not been tested.
MySQL, on the other hand, is **not recommended** due to various technical limitations.

View file

@ -0,0 +1,426 @@
# The configuration file
Depending on how The Arsse was installed, it will look for configuration in the following places:
| Installation method | Default configuration file path |
|---------------------|--------------------------------------------------------|
| Arch Linux package | `/etc/webapps/arsse/config.php` |
| Debian package | `/etc/arsse/config.php` |
| Manual installation | `config.php` in the The Arsse's installation directory |
It is not an error for this file not to exist or to be empty: The Arsse will function with no configuration whatsoever, provided other conditions allow.
The configuration file is a PHP script which returns an associative array with keys and values for one or more settings. Any settings which are not specified in the configuration file will be set to their defaults. Invalid values will cause an error on start-up, while unknown keys are ignored. A basic configuration file might look like this:
```php
<?php return [
'lang' => "en",
'dbDriver' => "sqlite3",
'dbSQLite3File' => "/var/lib/arsse/arsse.db",
];
```
# List of all settings
The `config.defaults.php` file included with copies of The Arsse contains an annotated listing of every configuration setting with its default value. The settings are also documented in more detail below.
## General settings
### lang
| Type | Default |
|--------|---------|
| string | `"en"` |
The default language locale, mostly used when reporting errors on the command line or in logs. Currently only `"en"` (English) is available.
## Database settings
### dbDriver
| Type | Default |
|--------|-------------|
| string | `"sqlite3"` |
The database driver to use. The following values are understood:
- `"sqlite3"` for SQLite databases
- `"postgresql"` for PostgreSQL databases
- `"mysql"` for MySQL databases
It is also possible to specify the fully-qualified name of a class which implements the database driver interface. For example, specifying `"JKingWeb\Arsse\Db\SQLite3\PDODriver"` would use the PDO driver for SQLite 3.
### dbAutoUpdate
| Type | Default |
|---------|---------|
| boolean | `true` |
Whether to attempt to automatically upgrade the database schema when upgrading The Arsse to a new version with schema changes.
If set to `false`, the database schema must be manually upgraded. Schema files can be found under `sql/<backend>/#.sql`; the `UPGRADING` file will advise when a schema upgrade is required.
### dbTimeoutConnect
| Type | Default |
|------------------|---------|
| interval or null | `5.0` |
The number of seconds to wait before returning a timeout error when connecting to a database server. The special value `null` waits the maximum amount of time, while `0.0` waits the minimum amount of time. The minimums are maximums for each backend are as follows:
| Backend | Minimum | Maximum |
|------------|----------|----------|
| SQLite 3 | *(does not apply)* | *(does not apply)* |
| PostgreSQL | 1 second | forever |
| MySQL | 1 second | forever |
Note that in practice neither PostgreSQL nor MySQL will wait indefinitely: they are still subject to PHP's socket timeouts. Consult [PostgreSQL's documentation](https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNECT-CONNECT-TIMEOUT) for details on how the timeout is interpreted by PostgreSQL.
### dbTimeoutExec
| Type | Default |
|----------|---------|
| interval | `null` |
The number of seconds to wait before returning a timeout error when executing a database operation (i.e. computing results). The special value `null` waits the maximum amount of time, while `0.0` waits the minimum amount of time. The minimums are maximums for each backend are as follows:
| Backend | Minimum | Maximum |
|------------|---------------|----------|
| SQLite 3 | *(does not apply)* | *(does not apply)* |
| PostgreSQL | 1 millisecond | forever |
| MySQL | 1 second | forever |
With MySQL this timeout only applies to read operations, whereas PostgreSQL will time out write operations as well.
### dbTimeoutLock
| Type | Default |
|----------|---------|
| interval | `60.0` |
The number of seconds to wait before returning a timeout error when acquiring database locks. The special value `null` waits the maximum amount of time, while `0.0` waits the minimum amount of time. The minimums are maximums for each backend are as follows:
| Backend | Minimum | Maximum |
|------------|----------------|-----------------------|
| SQLite 3 | 0 milliseconds | at least 24 days |
| PostgreSQL | 1 millisecond | forever |
| MySQL | 1 second | approximately 1 year |
Note that PostgreSQL counts time spent waiting for locks as part of the above execution timeout. The maximum timeout for SQLite is `PHP_INT_MAX` milliseconds, which on 32-bit systems is just under 25 days, and on 64-bit systems is billions of years.
## Database settings specific to SQLite 3
### dbSQLite3File
| Type | Default |
|----------------|---------|
| string or null | `null` |
The full path and file name of SQLite database. The special value `null` evaluates to a file named `"arsse.db"` in the directory where The Arsse is installed.
### dbSQLite3Key
| Type | Default |
|--------|---------|
| string | `""` |
The key used to encrypt/decrypt the SQLite database. This is only relevant if using the [SQLite Encryption Extension](https://www.sqlite.org/see/).
## Database settings specific to PostgreSQL
### dbPostgreSQLHost
| Type | Default |
|--------|---------|
| string | `""` |
The host name, address, or socket path of the PostgreSQL database server.
Consult [PostgreSQL's documentation](https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNECT-HOST) for more details.
### dbPostgreSQLUser
| Type | Default |
|--------|-----------|
| string | `"arsse"` |
The log-in user name for the PostgreSQL database server.
### dbPostgreSQLPass
| Type | Default |
|--------|---------|
| string | `""` |
The log-in password for the PostgreSQL database server.
### dbPostgreSQLPort
| Type | Default |
|---------|---------|
| integer | `5432` |
The TCP port on which to connect to the PostgreSQL database server, if connecting via TCP.
### dbPostgreSQLDb
| Type | Default |
|--------|-----------|
| string | `"arsse"` |
The name of the database used by The Arsse on the PostgreSQL database server.
### dbPostgreSQLSchema
| Type | Default |
|--------|---------|
| string | `""` |
The name of the schema used by The Arsse on the PostgreSQL database server. When not set to the empty string, the PostgreSQL search path is modified to consist of the specified schema with a fallback to the public schema.
Consult [PostgreSQL's documentation](https://www.postgresql.org/docs/current/ddl-schemas.html) for more details.
### dbPostgreSQLService
| Type | Default |
|--------|---------|
| string | `""` |
A PostgreSQL service file entry to use *instead of* the above configuration; if using a service entry all above PostgreSQL-specific parameters except schema are ignored.
Consult [PostgreSQL's documentation](https://www.postgresql.org/docs/current/libpq-pgservice.html) for more details.
## Database settings specific to MySQL
### dbMySQLHost
| Type | Default |
|--------|---------------|
| string | `"localhost"` |
The host name or address of the MySQL database server. The values `"localhost"` and `"."` are special.
Consult [MySQL's documentation](https://dev.mysql.com/doc/refman/8.0/en/connecting.html) for more details.
### dbMySQLUser
| Type | Default |
|--------|-----------|
| string | `"arsse"` |
The log-in user name for the MySQL database server.
### dbMySQLPass
| Type | Default |
|--------|---------|
| string | `""` |
The log-in password for the MySQL database server.
### dbMySQLPort
| Type | Default |
|---------|---------|
| integer | `3306` |
The TCP port on which to connect to the MySQL database server, if connecting via TCP.
### dbMySQLDb
| Type | Default |
|--------|-----------|
| string | `"arsse"` |
The name of the database used by The Arsse on the MySQL database server.
### dbMySQLSocket
| Type | Default |
|--------|---------|
| string | `""` |
A Unix domain socket or named pipe to use for the MySQL database server when not connecting via TCP.
## User management settings
### userDriver
| Type | Default |
|--------|--------------|
| string | `"internal"` |
The user management driver to use. Currently only `"internal"` is available, which stores user IDs and hashed passwords in the configured database.
It is also possible to specify the fully-qualified name of a class which implements the user management driver interface. For example, specifying `"JKingWeb\Arsse\User\Internal\Driver"` would use the internal driver.
### userPreAuth
| Type | Default |
|---------|---------|
| boolean | `false` |
Whether users are authenticated by the Web server before requests are passed to The Arsse. If set to `true` The Arsse will perform no HTTP-level authentication and assume that the user ID supplied in either the `REMOTE_USER` CGI variable or the `Authorization` HTTP header-field (if `Basic` authentication was used) is authentic.
For synchronization protocols which implement their own authentication (such as Tiny Tiny RSS), this setting may or may not affect how protocol-level authentication is handled; consult the section on The Arsse's [supported protocols](/en/Supported_Protocols) for more information.
If the user has not previously logged in, an entry is created for them in the database automatically. If the Web server uses `Basic` HTTP authentication and passes along the `Authorization` field, a hash of the user's password will also be stored in The Arsse's database.
### userHTTPAuthRequired
| Type | Default |
|---------|---------|
| boolean | `false` |
Whether to require successful HTTP authentication before processing API-level authentication, for protocol which implement their own authentication.
### userSessionEnforced
| Type | Default |
|---------|---------|
| boolean | `true` |
Whether invalid or expired API session tokens should prevent logging in when HTTP authentication is used, for protocol which implement their own authentication.
### userSessionTimeout
| Type | Default |
|----------|-----------|
| interval | `"PT24H"` |
The period of inactivity after which log-in sessions should be considered invalid. Session timeouts should not be made too long, to guard against session hijacking.
### userSessionLifetime
| Type | Default |
|----------|-----------|
| interval | `"P7D"` |
The maximum lifetime of log-in sessions regardless of recent activity. Session lifetimes should not be made too long, to guard against session hijacking.
### userTempPasswordLength
| Type | Default |
|---------|---------|
| integer | `20` |
The desired length in characters of randomly-generated user passwords. When [adding users](/en/Using_The_Arsse/Managing_Users), omitting a desired password generates a random one; this setting controls the length of these passwords.
## Newsfeed fetching service settings
### serviceDriver
| Type | Default |
|--------|----------------|
| string | `"subprocess"` |
The newsfeed fetching service driver to use. The following values are understood:
- `"serial"`: Fetches newsfeeds and processed them one at a time. This is the slowest method, but is simple and reliable.
- `"subprocess"`: Fetches and processes multiple newsfeeds concurrently by starting a separate process for each newsfeed using PHP's [`popen`](https://php.net/manual/en/function.popen.php) function. This uses more memory and processing power, but takes less total time.
It is also possible to specify the fully-qualified name of a class which implements the service driver interface. For example, specifying `"JKingWeb\Arsse\Service\Serial\Driver"` would use the serial driver.
### serviceFrequency
| Type | Default |
|----------|----------|
| interval | `"PT2M"` |
The interval the newsfeed fetching service observes between checks for new articles. Note that requests to foreign servers are not necessarily made at this frequency: each newsfeed is assigned its own time at which to be next retrieved. This setting instead defines the length of time the fetching service will sleep between periods of activity.
Consult "[How Often Newsfeeds Are Fetched](/en/Using_The_Arsse/Other_Topics#page_How_often_newsfeeds_are_fetched)" for details on newsfeed update frequency.
### serviceQueueWidth
| Type | Default |
|---------|---------|
| integer | `5` |
The maximum number of concurrent newsfeed updates to perform, if a concurrent service driver is used.
### fetchTimeout
| Type | Default |
|----------|---------|
| interval | `10.0` |
The maximum number of seconds to wait for data when fetching newsfeeds from foreign servers.
### fetchSizeLimit
| Type | Default |
|---------|-------------------|
| integer | `2 * 1024 * 1024` |
The maximum size, in bytes, of data to accept when fetching a newsfeed. Newsfeeds larger than this will be rejected to guard against denial-of-servioce attacks.
The default value is equal to two megabytes.
### fetchEnableScraping
| Type | Default |
|---------|---------|
| boolean | `true` |
Whether to allow the possibility of fetching full article contents from an article's source, if a newsfeed only provides excerpts. Whether fetching will actually happen is governed by a per-newsfeed toggle (defaulting to `false`) which currently can only be changed via the Miniflux protocol.
### fetchUserAgentString
| Type | Default |
|----------------|---------|
| string or null | `null` |
The [user agent](https://tools.ietf.org/html/rfc7231#section-5.5.3) The Arsse will identify as when fetching newsfeeds. The special value null will use an identifier similar to the following:
```
Arsse/0.6.0 (Linux 4.15.0; x86_64; https://thearsse.com/)
```
## Housekeeping settings
### purgeFeeds
| Type | Default |
|------------------|-----------|
| interval or null | `"PT24H"` |
How long to keep a newsfeed and its articles in the database after all its subscriptions have been deleted. Specifying `null` will retain unsubscribed newsfeeds forever, whereas an interval evaluating to zero (e.g. `"PT0S"`) will delete them immediately.
Note that articles of orphaned newsfeeds are still subject to the `purgeArticleUnread` threshold below.
### purgeArticlesRead
| Type | Default |
|------------------|---------|
| interval or null | `"P7D"` |
How long to keep a an article in the database after all users subscribed to its newsfeed have read it. Specifying `null` will retain articles up to the `purgeArticlesUnread` threshold below, whereas an interval evaluating to zero (e.g. `"PT0S"`) will delete them immediately.
If an article is starred by any user, it is retained indefinitely regardless of this setting.
This setting also governs when an article is hidden from a user after being read by that user, regardless of its actual presence in the database.
### purgeArticlesUnread
| Type | Default |
|------------------|----------|
| interval or null | `"P21D"` |
How long to keep a an article in the database regardless of whether any users have read it. Specifying `null` will retain articles forever, whereas an interval evaluating to zero (e.g. `"PT0S"`) will delete them immediately.
If an article is starred by any user, it is retained indefinitely regardless of this setting.
# Obsolete settings
### dbSQLite3Timeout
| Type | Historical Default |
|----------|--------------------|
| interval | `60.0` |
*This setting has been replaced by [dbTimeoutLock](#page_dbTimeoutLock).*
The number of seconds for SQLite to wait before returning a timeout error when trying to acquire a write lock on the database file. Setting this to a low value may cause operations to fail with an error.
Consult [SQLite's documentation](https://sqlite.org/c3ref/busy_timeout.html) for more details.

View file

@ -0,0 +1,88 @@
[TOC]
# Preface
This section describes in brief some CLI commands. Please read [the general notes on the command line interface](index) before continuing.
# Adding users
When first installed, The Arsse has no users configured. You may add users by executing the following command:
```sh
sudo arsse user add "user@example.com" "example password"
```
The password argument is optional: if no password is provided, a random one is generated and printed out:
```console
$ sudo arsse user add "jane.doe"
Ji0ivMYqi6gKxQK1MHuE
```
# Setting and changing passwords
Setting a user's password is nearly identical to adding a user:
```sh
sudo arsse user set-pass "user@example.com" "new password"
```
As when adding a user, the password argument is optional: if no password is provided, a random one is generated and printed out:
```console
$ sudo arsse user set-pass "jane.doe"
Ummn173XjbJT4J3Gnx0a
```
## Setting and changing passwords for Fever
Before a user can make use of [the Fever protocol](/en/Supported_Protocols/Fever), a Fever-specific password for that user must be set. It is _highly recommended_ that this not be the samer as the user's main password. The password can be set by adding the `--fever` option to the normal password-changing command:
```sh
sudo arsse user set-pass --fever "user@example.com" "fever password"
```
As when setting a main password, the password argument is optional: if no password is provided, a random one is generated and printed out:
```console
$ sudo arsse user set-pass --fever "jane.doe"
YfZJHq4fNTRUKDYhzQdR
```
## Managing login tokens for Miniflux
[Miniflux](/en/Supported_Protocols/Miniflux) clients may optionally log in using tokens: randomly-generated strings which act as persistent passwords. For now these must be generated using the command-line interface:
```console
$ sudo arsse token create "jane.doe"
xRK0huUE9KHNHf_x_H8JG0oRDo4t_WV44whBtr8Ckf0=
```
Multiple tokens may be generated for use with different clients, and descriptive labels can be assigned for later identification:
```console
$ sudo arsse token create "jane.doe" Newsflash
xRK0huUE9KHNHf_x_H8JG0oRDo4t_WV44whBtr8Ckf0=
$ sudo arsse token create "jane.doe" Reminiflux
L7asI2X_d-krinGJd1GsiRdFm2o06ZUlgD22H913hK4=
```
There are also commands for listing and revoking tokens. Please consult the integrated help for more details.
# Setting and changing user metadata
Users may also have various metadata properties set. These largely exist for compatibility with [the Miniflux protocol](/en/Supported_Protocols/Miniflux) and have no significant effect. One exception to this, however, is the `admin` flag, which signals whether the user may perform privileged operations where they exist in the supported protocols.
The flag may be changed using the following command:
```sh
sudo arsse user set "jane.doe" admin true
```
As a shortcut it is also possible to create administrators directly:
```sh
sudo arsse user add "user@example.com" "example password" --admin
```
Please consult the integrated help for more details on metadata and their effects.

View file

@ -0,0 +1,54 @@
[TOC]
# Preface
This section describes in brief some CLI commands. Please read [the general notes on the command line interface](index) before continuing.
# Importing newsfeeds from OPML
It's possible to import not only newsfeeds but also folders and Fever groups using OPML files. The process is simple:
```sh
sudo arsse import "user@example.com" "subscriptions.opml"
```
The importer is forgiving, but some OPML files may fail, with the reason printed out. Files are either imported in total, or not at all.
# Exporting newsfeeds to OPML
It's possible to export not only newsfeeds but also folders and Fever groups to OPML files. The process is simple:
```sh
sudo arsse export "user@example.com" "subscriptions.opml"
```
The output might look like this:
```xml
<opml version="2.0">
<head/>
<body>
<outline text="Folder">
<outline text="Subfolder">
<outline type="rss" text="Feed 1" xmlUrl="http://example.com/feed1"/>
</outline>
<outline type="rss" text="Feed 2" xmlUrl="http://example.com/feed2" category="group 1,group 2"/>
<outline type="rss" text="Feed 3" xmlUrl="http://example.com/feed3" category="group 1"/>
</outline>
<outline type="rss" text="Feed 4" xmlUrl="http://example.com/feed4" category="group 2,group 3"/>
</body>
</opml>
```
# Managing newsfeeds via OPML
Not all protocols supported by The Arsse allow modifying newsfeeds or folders, et cetera; additionally, not all clients support these capabilities even if the protocol has the necessary features. An OPML export/import sequence with the `--replace` import option specified, however, makes any kind of modification possible. For example:
```sh
# export your newsfeeds
sudo arsse export "user@example.com" "subscriptions.opml"
# make any changes you want in your editor of choice
nano "subscriptions.opml"
# re-import the modified information
sudo arsse import "user@example.com" "subscriptions.opml" --replace
```

View file

@ -0,0 +1,34 @@
[TOC]
# Preface
This section describes in brief some CLI commands. Please read [the general notes on the command line interface](index) before continuing.
# Refreshing newsfeeds with a cron job
Normally The Arsse has a systemd service which checks newsfeeds for updates and processes them into its database for the user. If for whatever reason this is not practical a [cron](https://en.wikipedia.org/wiki/Cron) job may be used instead.
Keeping newsfeeds updated with cron is not difficult. Simply run the following command:
```sh
sudo crontab -u arsse -e
```
And add a line such as this one:
```
*/2 * * * * /usr/bin/arsse refresh-all
```
Thereafter The Arsse's will be scheduled to check newsfeeds every two minutes. Consult the manual pages for the `crontab` [format](http://man7.org/linux/man-pages/man5/crontab.5.html) and [command](http://man7.org/linux/man-pages/man1/crontab.1.html) for details.
# How often newsfeeds are fetched
Though by default The Arsse will wake up every two minutes, newsfeeds are not actually downloaded so frequently. Instead, each newsfeed is assigned a time at which it should next be fetched, and once that time is reached a [conditional request](https://developer.mozilla.org/en-US/docs/Web/HTTP/Conditional_requests) is made. The interval between requests for a particular newsfeed can vary from 15 minutes to 24 hours based on multiple factors such as:
- The length of time since the newsfeed last changed
- The interval between publishing of articles in the newsfeed
- Whether the last fetch or last several fetches resulted in error
As a general rule, newsfeeds which change frequently are checked frequently, and those which change seldom are fetched at most daily.

View file

@ -0,0 +1,9 @@
# Preface
This section details a few administrative tasks which may need to be performed after installing The Arsse. As no Web-based administrative interface is included, these tasks are generally performed via command line interface.
Though this section describes some commands briefly, complete documentation of The Arsse's command line interface is not included in this manual. Documentation for CLI commands can instead be viewed using the system manual service by executing `man arsse`.
# A Note on Command Invocation
Particularly if using an SQLite database, it's important that administrative commands be executed as the same user who owns The Arsse's files. To that end our releases include an `arsse` executable which drops privileges when executed as root. Commands in this section assume this executable is being used.

View file

@ -0,0 +1,59 @@
[TOC]
# About
<dl>
<dt>Supported since</dt>
<dd>0.9.0</dd>
<dt>Base URL</dt>
<dd>/</dd>
<dt>API endpoint</dt>
<dd>/v1/</dd>
<dt>Specifications</dt>
<dd><a href="https://miniflux.app/docs/api.html">API Reference</a>, <a href="https://miniflux.app/docs/rules.html#filtering-rules">Filtering Rules</a></dd>
</dl>
The Miniflux protocol is a fairly well-designed protocol supporting a wide variety of operations on newsfeeds, folders (termed "categories"), and articles; it also allows for user administration, and native OPML importing and exporting. Architecturally it is similar to the Nextcloud News protocol, but has more capabilities.
Miniflux version 2.0.28 is emulated, though not all features are implemented
# Missing features
- JSON Feed format is not suported
- Various feed-related features are not supported; attempting to use them has no effect
- Rewrite rules and scraper rules
- Custom User-Agent strings
- The `disabled`, `ignore_http_cache`, and `fetch_via_proxy` flags
- Changing the URL, username, or password of a feed
- Manually refreshing feeds
- Titles and types are not available during feed discovery and are filled with generic data
- Reading time is not calculated and will always be zero
- Only the first enclosure of an article is retained
- Comment URLs of articles are not exposed
# Differences
- Various error codes and messages differ due to significant implementation differences
- The "All" category is treated specially (see below for details)
- Feed and category titles consisting only of whitespace are rejected along with the empty string
- Filtering rules may not function identically (see below for details)
- The `checked_at` field of feeds indicates when the feed was last updated rather than when it was last checked
- Creating a feed with the `scrape` property set to `true` might not return scraped content for the initial synchronization
- Search strings will match partial words
- OPML import either succeeds or fails atomically: if one feed fails, no feeds are imported
# Behaviour of filtering (block and keep) rules
The Miniflux documentation gives only a brief example of a pattern for its filtering rules; the allowed syntax is described in full [in Google's documentation for RE2](https://github.com/google/re2/wiki/Syntax). Being a PHP application, The Arsse instead accepts [PCRE syntax](http://www.pcre.org/original/doc/html/pcresyntax.html) (or since PHP 7.3 [PCRE2 syntax](https://www.pcre.org/current/doc/html/pcre2syntax.html)), specifically in UTF-8 mode. Delimiters should not be included, and slashes should not be escaped; anchors may be used if desired. For example `^(?i)RE/MAX$` is a valid pattern.
For convenience the patterns are tested after collapsing whitespace. Unlike Miniflux, The Arsse tests the patterns against an article's author-supplied categories if they do not match its title. Also unlike Miniflux, when filter rules are modified they are re-evaluated against all applicable articles immediately.
# Special handling of the "All" category
Nextcloud News' root folder and Tiny Tiny RSS' "Uncategorized" catgory are mapped to Miniflux's initial "All" category. This Miniflux category can be renamed, but it cannot be deleted. Attempting to do so will delete the child feeds it contains, but not the category itself.
Because the root folder does not existing in the database as a separate entity, it will always sort first when ordering by `category_id` or `category_title`.
# Interaction with nested categories
Tiny Tiny RSS is unique in allowing newsfeeds to be grouped into categories nested to arbitrary depth. When newsfeeds are placed into nested categories, they simply appear in the top-level category when accessed via the Miniflux protocol. This does not affect OPML exports, where full nesting is preserved.

View file

@ -0,0 +1,38 @@
[TOC]
# About
<dl>
<dt>Supported since</dt>
<dd>0.1.0</dd>
<dt>Base URL</dt>
<dd>/</dd>
<dt>API endpoint</dt>
<dd>/index.php/apps/news/api/v1-2/</dd>
<dt>Specifications</dt>
<dd><a href="https://github.com/nextcloud/news/blob/master/docs/api/api-v1-2.md">Version 1.2</a></dd>
</dl>
The Nextcloud News protocol was the first supported by The Arsse, and has been supported in full since version 0.3.0.
It allows organizing newsfeeds into single-level folders, and supports a wide range of operations on newsfeeds, folders, and articles.
# Differences
- Article GUID hashes are not hashes like in NCN; they are integers rendered as strings
- Article fingerprints are a combination of hashes rather than a single hash
- When marking articles as starred the feed ID is ignored, as they are not needed to establish uniqueness
- The feed updater ignores the `userId` parameter: feeds in The Arsse are deduplicated, and have no owner
- The `/feeds/all` route lists only feeds which should be checked for updates, and it also returns all `userId` attributes as empty strings: feeds in The Arsse are deduplicated, and have no owner
- The "updater" console commands mentioned in the protocol specification are not implemented, as The Arsse does not implement the required Nextcloud subsystems
- The `lastLoginTimestamp` attribute of the user metadata is always the current time: The Arsse's implementation of the protocol is fully stateless
- Syntactically invalid JSON input will yield a `400 Bad Request` response instead of falling back to GET parameters
- Folder names consisting only of whitespace are rejected along with the empty string
- Feed titles consisting only of whitespace or the empty string are rejected with a `422 Unprocessable Entity` reponse instead of being accepted
- Bulk-marking operations without a `newestItemId` argument result in a `422 Unprocessable Entity` reponse instead of silently failing
- Creating a feed in a folder which does not exist places the feed in the root folder rather than suppressing the feed
- Moving a feed to a folder which does not exist results in a `422 Unprocessable Entity` reponse rather than suppressing the feed
# Interaction with nested folders
Tiny Tiny RSS is unique in allowing newsfeeds to be grouped into folders nested to arbitrary depth. When newsfeeds are placed into nested folders, they simply appear in the top-level folder when accessed via the Nextcloud News protocol.

View file

@ -0,0 +1,95 @@
[TOC]
# About
<dl>
<dt>Supported since</dt>
<dd>0.2.0</dd>
<dt>Base URL</dt>
<dd>/tt-rss/</dd>
<dt>API endpoint</dt>
<dd>/tt-rss/api</dd>
<dt>Specifications</dt>
<dd><a href="https://tt-rss.org/wiki/ApiReference">Main</a>, <a href="https://tt-rss.org/wiki/SearchSyntax">search syntax</a>, <a href="https://github.com/jangernert/FeedReader/blob/master/data/tt-rss-feedreader-plugin/README.md">FeedReader extensions</a>, <a href="https://github.com/hrk/tt-rss-newsplus-plugin/blob/master/README.md">News+ extension</a></dd>
</dl>
The Arsse supports not only the Tiny Tiny RSS protocol, but also extensions required by the FeedReader client and the more commonly supported `getCompactHeadlines` extension.
It allows organizing newsfeeds into nested folders, and supports an odd patchwork subset of Tiny Tiny RSS' full capabilities. The FeedReader extensions round out the protocol with significantly more features. Unlike with TT-RSS itself, API access is always enabled with The Arsse.
# Missing features
The Arsse does not currently support the entire protocol. Notably missing features include manipulation of the special "Published" newsfeed, as well as searching. The full list of missing features is as follows:
- The `shareToPublished` operation is not implemented; it returns `UNKNOWN_METHOD`
- Setting an article's "published" flag with the `updateArticle` operation is not implemented and will gracefully fail
- The `sanitize`, `force_update`, and `has_sandbox` parameters of the `getHeadlines` operation are ignored
- String `feed_id` values for the `getCompactHeadlines` operation are not supported and will yield an `INCORRECT_USAGE` error
- Articles are limited to a single attachment rather than multiple attachments
- The `getPref` operation is not implemented; it returns `UNKNOWN_METHOD`
# Differences
- Input that cannot be parsed as JSON normally returns a `NOT_LOGGED_IN` error; The Arsse returns a non-standard `MALFORMED_INPUT` error instead
- Feed, category, and label names are normally unrestricted; The Arsse rejects empty strings, as well as strings composed solely of whitespace
- Discovering multiple feeds during `subscribeToFeed` processing normally produces an error; The Arsse instead chooses the first feed it finds
- Providing the `setArticleLabel` operation with an invalid label normally silently fails; The Arsse returns an `INVALID_USAGE` error instead
- Processing of the `search` parameter of the `getHeadlines` operation differs in the following ways:
- Values other than `"true"` or `"false"` for the `unread`, `star`, and `pub` special keywords treat the entire token as a search term rather than as `"false"`
- Invalid dates are ignored rather than assumed to be `"1970-01-01"`
- Specifying multiple non-negative dates usually returns no results as articles must match all specified dates simultaneously; The Arsse instead returns articles matching any of the specified dates
- Dates are always relative to UTC
- Full-text search is not yet employed with any database, including PostgreSQL
- Article hashes are normally SHA1; The Arsse uses SHA256 hashes
- Article attachments normally have unique IDs; The Arsse always gives attachments an ID of `"0"`
- The `getCounters` operation normally omits members with zero unread; The Arsse includes everything to appease some clients
# Other notes
- TT-RSS accepts base64-encoded passwords, though this is undocumented; The Arsse accepts base64-encoded passwords as well
- TT-RSS sometimes returns an incorrect count from the `setArticleLabel` operation; The Arsse returns a correct count in all cases
- TT-RSS sometimes returns out-of-date cached information; The Arsse does not use caches as TT-RSS does, so information is always current
- TT-RSS returns results for _feed_ ID `-3` when providing the `getHeadlines` operation with _category_ ID `-3`; The Arsse retuns the correct results
- The protocol doucmentation advises not to use `limit` or `skip` together with `unread_only` for the `getFeeds` operation as it produces unpredictable results; The Arsse produces predictable results by first retrieving all unread feeds and then applying `skip` and `limit`
- The protocol documentation on values for the `view_mode` parameter of the `getHeadlines` operation is out of date; The Arsse matches the actual implementation and supports the undocumented `published` and `has_note` values exposed by the Web user interface
- The protocol documentation makes mention of a `search_mode` parameter for the `getHeadlines` operation, but this seems to be ignored; The Arsse does not implement it
- The protocol documentation makes mention of an `output_mode` parameter for the `getCounters` operation, but this seems to be ignored; The Arsse does not implement it
- The documentation for the `getCompactHeadlines` operation states the default value for `limit` is 20, but the reference implementation defaults to unlimited; The Arsse also defaults to unlimited
- It is assumed TT-RSS exposes other undocumented behaviour; unless otherwise noted The Arsse only implements documented behaviour
# Interaction with HTTP authentication
Tiny Tiny RSS itself is unaware of HTTP authentication: if HTTP authentication is used in the server configuration, it has no effect on authentication in the API. The Arsse, however, makes use of HTTP authentication for Nextcloud News, and can do so for TT-RSS as well. In a default configuration The Arsse functions in the same way as TT-RSS: HTTP authentication and API authentication are completely separate and independent. Alternative behaviour is summarized below:
- With default settings:
- Clients may optionally provide HTTP credentials
- API authentication proceeds as normal
- All feed icons are visible to unauthenticated clients
- Analogous to multi-user mode
- If the `userHTTPAuthRequired` setting is `true`:
- Clients must pass HTTP authentication
- API authentication proceeds as normal
- Feed icons are visible only to their owners
- Analoguous to multi-user mode with additional HTTP authentication
- If the `userSessionEnforced` setting is `false`:
- Clients may optionally provide HTTP credentials
- If HTTP authentication succeeded API authentication is skipped: tokens are issued upon login, but ignored for HTTP-authenticated requests
- All feed icons are visible to unauthenticated clients
- Analogous to single-user mode if using HTTP authentication, and to multi-user mode otherwise
- If the `userHTTPAuthRequired` setting is `true` and the `userSessionEnforced` setting is `false`:
- Clients must pass HTTP authentication
- API authentication is skipped: tokens are issued upon login, but thereafter ignored
- Feed icons are visible only to their owners
- Analogous to single-user mode
- If the `userPreAuth` setting is `true`:
- The Web server asserts HTTP authentication was successful
- API authentication only checks that HTTP and API user names match
- Feed icons are visible only to their owners
- Analoguous to multi-user mode with additional HTTP authentication
- If the `userPreAuth` setting is `true` and the `userSessionEnforced` setting is `false`:
- The Web server asserts HTTP authentication was successful
- API authentication is skipped: tokens are issued upon login, but thereafter ignored
- Feed icons are visible only to their owners
- Analogous to single-user mode
In all cases, supplying invalid HTTP credentials will result in a 401 response.

View file

@ -0,0 +1,44 @@
[TOC]
# About
<dl>
<dt>Supported since</dt>
<dd>0.8.0</dd>
<dt>Base URL</dt>
<dd>/fever/</dd>
<dt>API endpoint</dt>
<dd>/fever/?api</dd>
<dt>Specifications</dt>
<dd><a href="https://web.archive.org/web/20161217042229/https://feedafever.com/api">"Public beta"</a> (at the Internet Archive)</dd>
</dl>
The Fever protocol is a basic protocol which has historically been popular with iOS and macOS clients.
It allows marking articles as read or starred, but does not allow adding or modifying newsfeeds. Moreover, instead of being classified into folders, newfeeds may belong to multiple groups, which do not nest.
# Missing features
The Fever protocol is incomplete, unusual, _and_ a product of proprietary software which is no longer available. Conssequently some features have been omitted either out of necessity or because implementation details made the effort required too great.
- All feeds are considered "Kindling"
- The "Hot Links" feature is not implemented; when requested, an empty array will be returned. As there is no way to classify a feed as a "Spark" in the protocol itself and no documentation exists on how link temperature was calculated, an implementation is unlikely to appear in the future
# Special considerations
- Because of Fever's unusual and insecure authentication scheme, a Fever-specific password [must be created](/en/Using_The_Arsse/Managing_Users) before a user can communicate via the Fever protocol
- The Fever protocol does not allow for adding or modifying feeds. Another protocol or OPML importing must be used to manage feeds
- Unlike other protocols supported by The Arsse, Fever uses "groups" (more commonly known as tags or labels) instead of folders to organize feeds. Currently [OPML importing](/en/Using_The_Arsse/Importing_and_Exporting) is the only means of managing groups
# Other notes
- The undocumented `group_ids`, `feed_ids`, and `as=unread` parameters are all supported
- XML output is supported, but may not behave as Fever did. Its use by clients is discouraged
# Interaction with HTTP Authentication
Fever was not designed with HTTP authentication in mind, and few clients respond to challenges. If the Web server or The Arsse is configured to require successful HTTP authentication, most Fever clients are not likely to be able to connect properly.
# Interaction with Folders
Unlike other protocols supported by The Arsse, Fever uses "groups" (more commonly known as tags or labels) to organize newsfeeds. These are fully supported and are exposed as categories in OPML import and export. They are treated separately from folders.

View file

@ -0,0 +1,8 @@
The Arsse was designed from the start as a server for multiple synchronization protocols which clients can make use of. Currently the following protocols are supported:
- [Miniflux](Miniflux)
- [Nextcloud News](Nextcloud_News)
- [Tiny Tiny RSS](Tiny_Tiny_RSS)
- [Fever](Fever)
The protocols are merely different ways of accessing and manipulating the same data: subscribing to a newsfeed using one protocol will see the newsfeed also present when later connecting via another protocol, for example. Because not all protocols work according to the same concepts, however, sometimes the interactions between them are not obvious. These interactions are documented here when warranted.

View file

@ -0,0 +1,472 @@
The Arsse does not at this time have any first party clients. However, because The Arsse [supports existing protocols](/en/Supported_Protocols), most clients built for these protocols are compatible with The Arsse. Below are those that we personally know of and have tested with The Arsse, presented in alphabetical order.
<table class="clients">
<thead>
<tr>
<th rowspan="2">Name</th>
<th rowspan="2">OS</th>
<th colspan="4">Protocol</th>
<th rowspan="2">Notes</th>
</tr>
<tr>
<th>Miniflux</th>
<th>Nextcloud News</th>
<th>Tiny Tiny RSS</th>
<th>Fever</th>
</tr>
</thead>
<tbody>
<th colspan="7">Web</th>
<tr>
</tr>
<tr>
<td><a href="https://github.com/yurikhan/miniflux-reader">Miniflux Reader</a></td>
<td></td>
<td class="Y"></td>
<td class="N"></td>
<td class="N"></td>
<td class="N"></td>
<td>Functional, but has some display glitches.</td>
</tr>
<tr>
<td><a href="https://github.com/jgerstbe/nx-news-ion">NX News</a></td>
<td></td>
<td class="N"></td>
<td class="Y"></td>
<td class="N"></td>
<td class="N"></td>
<td>Extremely basic client.</td>
</tr>
<tr>
<td><a href="https://github.com/reminiflux/reminiflux">reminiflux</a></td>
<td></td>
<td class="Y"></td>
<td class="N"></td>
<td class="N"></td>
<td class="N"></td>
<td>
<p>Three-pane alternative front-end for Minflux. Does not include functionality for managing feeds. Requires token authentication.</p>
</td>
</tr>
<tr>
<td><a href="https://github.com/kucrut/ttrss-reader">Tiny Tiny RSS Reader</a></td>
<td></td>
<td class="N"></td>
<td class="N"></td>
<td class="Y"></td>
<td class="N"></td>
<td>
<p></p>
</td>
</tr>
<tr>
<td><a href="https://github.com/TheScientist/ttrss-pwa">Tiny Tiny RSS Progressive Web App</a></td>
<td></td>
<td class="N"></td>
<td class="N"></td>
<td class="Y"></td>
<td class="N"></td>
<td>
<p>Does not (<a href="https://github.com/TheScientist/ttrss-pwa/issues/7">yet</a>) support HTTP authentication. Does not include functionality for managing feeds.</p>
</td>
</tr>
</tbody>
<tbody>
<tr>
<th colspan="7">Desktop</th>
</tr>
<tr>
<td><a href="https://jangernert.github.io/FeedReader/">FeedReader</a></td>
<td>Linux</td>
<td class="N"></td>
<td class="Y"></td>
<td class="Y"></td>
<td class="N"></td>
<td>
<p>Excellent reader; discontinued in favour of NewsFlash.</p>
<p>Not compatible with HTTP authentication when using TT-RSS.</p>
</td>
</tr>
<tr>
<td><a href="https://lzone.de/liferea/">Liferea</a></td>
<td>Linux</td>
<td class="N"></td>
<td class="N"></td>
<td class="Y"></td>
<td class="N"></td>
<td>
<p>Not compatible with HTTP authentication.</p>
</td>
</tr>
<tr>
<td><a href="https://newsboat.org/">Newsboat</a></td>
<td>Linux, macOS</td>
<td class="Y"></td>
<td class="Y"></td>
<td class="Y"></td>
<td class="N"></td>
<td>
<p>Terminal-based client.</p>
</td>
</tr>
<tr>
<td><a href="https://gitlab.com/news-flash/news_flash_gtk">NewsFlash</a></td>
<td>Linux</td>
<td class="Y"></td>
<td class="N"></td>
<td class="N"></td>
<td class="Y"></td>
<td>
<p>Successor to FeedReader. One of the best on any platform</p>
</td>
</tr>
<tr>
<td><a href="https://reeder.app/">Reeder 3</a></td>
<td>macOS</td>
<td class="N"></td>
<td class="N"></td>
<td class="N"></td>
<td class="Y"></td>
<td>
<p>Also available for iOS. Reeder 5 no longer supports the Fever protocol.</p>
</td>
</tr>
<tr>
<td><a href="https://github.com/martinrotter/rssguard/">RSS Guard</a></td>
<td>Windows, macOS, Linux</td>
<td class="N"></td>
<td class="Y"></td>
<td class="Y"></td>
<td class="N"></td>
<td>
<p>Very basic client.</p>
</td>
</tr>
</tr>
<tr>
<td><a href="https://bitbucket.org/thescientist/tiny-tiny-rss-wp8-client/src/master/">Tiny Tiny RSS Reader</td>
<td>Windows</td>
<td class="N"></td>
<td class="N"></td>
<td class="Y"></td>
<td class="N"></td>
<td>
<p>Does not deal well with expired sessions; discontinued.</p>
</td>
</tr>
</tbody>
<tbody>
<tr>
<th colspan="7">Mobile</th>
</tr>
<tr>
<td><a href="https://peterandlinda.com/cloudnews/">CloudNews</a></td>
<td>iOS</td>
<td class="N"></td>
<td class="Y"></td>
<td class="N"></td>
<td class="N"></td>
<td>
<p>Very bland looking application, but it functions well.</p>
</td>
</tr>
<tr>
<td><a href="https://play.google.com/store/apps/details?id=com.seazon.feedme">FeedMe</a></td>
<td>Android</td>
<td class="N"></td>
<td class="N"></td>
<td class="Y"></td>
<td class="N"></td>
<td>
<p>Not compatible with HTTP authentication.</p>
</td>
</tr>
<tr>
<td><a href="http://cocoacake.net/apps/fiery/">Fiery Feeds</a></td>
<td>iOS</td>
<td class="N"></td>
<td class="N"></td>
<td class="Y"></td>
<td class="Y"></td>
<td>
<p>Rentalware - For the software to be usable (you can't even add feeds otherwise) a subscription fee must be paid.</p>
<p>Support HTTP authentication with Fever.</p>
<p>Currently keeps showing items in the unread badge which have already been read.</p>
</td>
</tr>
<tr>
<td><a href="https://github.com/fbarthelery/geekttrss">Geekttrss</a></td>
<td>Android</td>
<td class="N"></td>
<td class="N"></td>
<td class="Y"></td>
<td class="N"></td>
<td>
<p></p>
</td>
</tr>
<tr>
<td><a href="https://play.google.com/store/apps/details?id=com.constantin.microflux">Microflux for Miniflux</a></td>
<td>Android</td>
<td class="Y"></td>
<td class="N"></td>
<td class="N"></td>
<td class="N"></td>
<td></td>
</tr>
<tr>
<td><a href="https://play.google.com/store/apps/details?id=mobi.newsjet.rss">NewsJet RSS</a></td>
<td>Android</td>
<td class="N"></td>
<td class="N"></td>
<td class="Y"></td>
<td class="N"></td>
<td></td>
</tr>
<tr>
<td><a href="https://github.com/SimonSchubert/NewsOut">Newsout</a></td>
<td>Android, iOS</td>
<td class="N"></td>
<td class="Y"></td>
<td class="N"></td>
<td class="N"></td>
<td>
<p>iOS version only as source code.</p>
</td>
</tr>
<tr>
<td><a href="https://github.com/nextcloud/news-android/">Nextcloud News</a></td>
<td>Android</td>
<td class="N"></td>
<td class="Y"></td>
<td class="N"></td>
<td class="N"></td>
<td>
<p>Official Android client for Nextcloud News.</p>
</td>
</tr>
<tr>
<td><a href="https://github.com/schaal/ocreader/">OCReader</a></td>
<td>Android</td>
<td class="N"></td>
<td class="Y"></td>
<td class="N"></td>
<td class="N"></td>
<td></td>
</tr>
<tr>
<td><a href="https://play.google.com/store/apps/details?id=com.isaiasmatewos.readably">Readably</a></td>
<td>Android</td>
<td class="N"></td>
<td class="N"></td>
<td class="N"></td>
<td class="Y"></td>
<td>
<p>Fetches favicons independently.</p>
</td>
</tr>
<tr>
<td><a href="https://github.com/readrops/Readrops">Readrops</a></td>
<td>Android</td>
<td class="N"></td>
<td class="Y"></td>
<td class="N"></td>
<td class="N"></td>
<td></td>
</tr>
<tr>
<td><a href="https://github.com/ali322/reed">Reed</a></td>
<td>Android</td>
<td class="Y"></td>
<td class="N"></td>
<td class="N"></td>
<td class="N"></td>
<td>
<p>Binaries only available from GitHub.</p>
</td>
</tr>
<tr>
<td><a href="https://reeder.app/">Reeder</a></td>
<td>iOS</td>
<td class="N"></td>
<td class="N"></td>
<td class="N"></td>
<td class="Y"></td>
<td>
<p>Also available for macOS.</p>
</td>
</tr>
<tr>
<td><a href="http://tt-rss.org/">Tiny Tiny RSS</a></td>
<td>Android</td>
<td class="N"></td>
<td class="N"></td>
<td class="Y"></td>
<td class="N"></td>
<td>
<p>Official Android client for Tiny Tiny RSS.</p>
</td>
</tr>
<tr>
<td><a href="http://github.com/nilsbraden/ttrss-reader-fork/">TTRSS-Reader</a></td>
<td>Android</td>
<td class="N"></td>
<td class="N"></td>
<td class="Y"></td>
<td class="N"></td>
<td>
<p></p>
</td>
</tr>
<tr>
<td><a href="https://www.goldenhillsoftware.com/unread/">Unread</a></td>
<td>iOS</td>
<td class="N"></td>
<td class="N"></td>
<td class="N"></td>
<td class="Y"></td>
<td>
<p>Trialware with one-time purchase.</p>
</td>
</tr>
</tbody>
</table>
## Untested clients
<table class="clients">
<thead>
<tr>
<th rowspan="2">Name</th>
<th rowspan="2">OS</th>
<th colspan="4">Protocol</th>
<th rowspan="2">Notes</th>
</tr>
<tr>
<th>Miniflux</th>
<th>Nextcloud News</th>
<th>Tiny Tiny RSS</th>
<th>Fever</th>
</tr>
</thead>
<tbody>
<tr>
<td><a href="https://github.com/jeena/feedthemonkey">FeedTheMonkey</a></td>
<td>Linux</td>
<td class="N"></td>
<td class="N"></td>
<td class="Y"></td>
<td class="N"></td>
<td>
<p></p>
</td>
</tr>
<tr>
<td><a href="https://github.com/Huessenbergnetz/Fuoten">Fuoten</a></td>
<td>Sailfish</td>
<td class="N"></td>
<td class="Y"></td>
<td class="N"></td>
<td class="N"></td>
<td>
<p></p>
</td>
</tr>
<tr>
<td><a href="https://github.com/mkiol/kaktus">Kaktus</a></td>
<td>Sailfish, BlackBerry</td>
<td class="N"></td>
<td class="N"></td>
<td class="Y"></td>
<td class="N"></td>
<td>
<p></p>
</td>
</tr>
<!--
<tr>
<td><a href="https://github.com/fabienheureux/liseur">Liseur</a></td>
<td>Windows?</td>
<td class="Y"></td>
<td class="N"></td>
<td class="N"></td>
<td class="N"></td>
<td>
<p>Level of functionality unclear.</p>
</td>
</tr>
<tr>
<td><a href="https://github.com/jakobend/maxiflux">maxiflux</a></td>
<td>Web</td>
<td class="Y"></td>
<td class="N"></td>
<td class="N"></td>
<td class="N"></td>
<td>Level of functionality unclear.</td>
</tr>
-->
<tr>
<td><a href="https://github.com/DocMarty84/miniflutt">Miniflutt</a></td>
<td>Android</td>
<td class="Y"></td>
<td class="N"></td>
<td class="N"></td>
<td class="N"></td>
<td>Does not display articles (<a href="https://github.com/DocMarty84/miniflutt/issues/3">see bug</a>)</td>
</tr>
<tr>
<td><a href="https://open-store.io/app/newsie.martinferretti">Newsie</a></td>
<td>Ubuntu Touch</td>
<td class="N"></td>
<td class="Y"></td>
<td class="N"></td>
<td class="N"></td>
<td>
</td>
</tr>
<tr>
<td><a href="https://readkitapp.com/">ReadKit</a></td>
<td>macOS</td>
<td class="N"></td>
<td class="N"></td>
<td class="N"></td>
<td class="Y"></td>
<td>
<p>Requires purchase. Presumed to work.</p>
</td>
</tr>
<tr>
<td><a href="https://github.com/matoung/SparkReader">SparkReader</a></td>
<td>Windows</td>
<td class="N"></td>
<td class="N"></td>
<td class="N"></td>
<td class="Y"></td>
<td>
<p>Requires manual configuration.</p>
</td>
</tr>
<tr>
<td><a href="http://www.pluchon.com/en/tiny_reader_rss.php">tiny Reader RSS</a></td>
<td>iOS</td>
<td class="N"></td>
<td class="N"></td>
<td class="Y"></td>
<td class="N"></td>
<td>
<p>Does not support HTTP authentication.</p>
</td>
</tr>
<tr>
<td><a href="https://github.com/cnlpete/ttrss">ttrss</a></td>
<td>Sailfish</td>
<td class="N"></td>
<td class="N"></td>
<td class="Y"></td>
<td class="N"></td>
<td>
<p></p>
</td>
</tr>
</tbody>
</table>

1
docs/index.md Normal file
View file

@ -0,0 +1 @@
Welcome to the user manual for The Advanced RSS Environment. It is included with each copy of the software, and is also [available online](https://thearsse.com/manual/en/). Please select a language above.

28
docs/theme/arsse/arsse.css vendored Normal file

File diff suppressed because one or more lines are too long

9
docs/theme/arsse/config.json vendored Normal file
View file

@ -0,0 +1,9 @@
{
"favicon": "<theme_url>favicon.png",
"js": [
"<theme_url>daux.min.js"
],
"css": [
"<theme_url>arsse.css"
]
}

2
docs/theme/arsse/daux.min.js vendored Normal file
View file

@ -0,0 +1,2 @@
var e=document.querySelectorAll(".s-content pre"),t=document.querySelector(".CodeToggler"),n="daux_code_blocks_hidden";function a(t){for(var a=0;a<e.length;a++)e[a].classList.toggle("Hidden",t);try{localStorage.setItem(n,t)}catch(e){}}t&&(e.length?function(){var e=t.querySelector(".CodeToggler__button--main");e.addEventListener("change",(function(e){a(!e.target.checked)}),!1);var r=!1;try{"false"===(r=localStorage.getItem(n))?r=!1:"true"===r&&(r=!0),r&&(a(!!r),e.checked=!r)}catch(e){}}():t.classList.add("Hidden"));var r=document.querySelector(".Collapsible__trigger");if(r){var o=document.querySelector(".Collapsible__content");r.addEventListener("click",(function(e){o.classList.contains("Collapsible__content--open")?(o.style.height=0,o.classList.remove("Collapsible__content--open"),r.setAttribute("aria-expanded","false")):(r.setAttribute("aria-expanded","true"),o.style.transitionDuration="150ms",o.style.height="".concat(o.scrollHeight,"px"),o.classList.add("Collapsible__content--open"))}))}var l=document.querySelectorAll("pre > code:not(.hljs)");if(l.length){var i=document.getElementsByTagName("head")[0],c=document.createElement("script");c.type="text/javascript",c.async=!0,c.src="".concat(window.base_url,"daux_libraries/highlight.pack.js"),c.onload=function(e){[].forEach.call(l,window.hljs.highlightBlock)},i.appendChild(c)}function s(e){var t=void 0!==e.preventDefault;t&&e.preventDefault();var n=function(e){for(var t=e;(t=t.parentNode)&&9!==t.nodeType;)if(1===t.nodeType&&t.classList.contains("Nav__item"))return t;throw new Error("Could not find a NavItem...")}(e.target),a=n.querySelector("ul.Nav");t&&n.classList.contains("Nav__item--open")?(a.style.height="".concat(a.scrollHeight,"px"),a.style.transitionDuration="150ms",a.style.height="0px",n.classList.remove("Nav__item--open")):t?(a.style.transitionDuration="150ms",a.addEventListener("transitionend",(function e(t){"0px"!==t.target.style.height&&(t.target.style.height="auto"),t.target.removeEventListener("transitionend",e)})),a.style.height="".concat(a.scrollHeight,"px"),n.classList.add("Nav__item--open")):a.style.height="auto"}for(var d,u=document.querySelectorAll(".Nav__item.has-children i.Nav__arrow"),h=u.length-1;h>=0;h--)(d=u[h]).addEventListener("click",s),d.parentNode.parentNode.classList.contains("Nav__item--open")&&s({target:d});var g=document.querySelectorAll(".Nav__item__link--nopage"),v=!0,p=!1,_=void 0;try{for(var y,m=g[Symbol.iterator]();!(v=(y=m.next()).done);v=!0){y.value.addEventListener("click",s)}}catch(e){p=!0,_=e}finally{try{v||null==m.return||m.return()}finally{if(p)throw _}}
//# sourceMappingURL=daux.min.js.map

BIN
docs/theme/arsse/fonts/cabin-bold.woff vendored Executable file

Binary file not shown.

BIN
docs/theme/arsse/fonts/cabin-bold.woff2 vendored Executable file

Binary file not shown.

BIN
docs/theme/arsse/fonts/cabin-italic.woff vendored Executable file

Binary file not shown.

BIN
docs/theme/arsse/fonts/cabin-italic.woff2 vendored Executable file

Binary file not shown.

BIN
docs/theme/arsse/fonts/cabin-regular.woff vendored Executable file

Binary file not shown.

BIN
docs/theme/arsse/fonts/cabin-regular.woff2 vendored Executable file

Binary file not shown.

BIN
docs/theme/arsse/fonts/leaguegothic.woff vendored Normal file

Binary file not shown.

Binary file not shown.

332
docs/theme/src/arsse.scss vendored Normal file
View file

@ -0,0 +1,332 @@
/* Daux imports; fonts are omitted */
@import "../../../vendor-bin/daux/vendor/daux/daux.io/src/css/theme_daux/vendor/normalize.scss";
@import "../../../vendor-bin/daux/vendor/daux/daux.io/src/css/theme_daux/_variables.scss";
@import "../../../vendor-bin/daux/vendor/daux/daux.io/src/css/theme_daux/_mixins.scss";
@import "../../../vendor-bin/daux/vendor/daux/daux.io/src/css/theme_daux/_structure.scss";
@import "../../../vendor-bin/daux/vendor/daux/daux.io/src/css/theme_daux/_typography.scss";
@import "../../../vendor-bin/daux/vendor/daux/daux.io/src/css/theme_daux/_components.scss";
@import "../../../vendor-bin/daux/vendor/daux/daux.io/src/css/theme_daux/_homepage.scss";
@import "../../../vendor-bin/daux/vendor/daux/daux.io/src/css/theme_daux/_print.scss" print;
/* The Arsse overrides */
.DarkModeToggler {
display: none;
}
@font-face {
font-family: 'League Gothic';
src: url('fonts/leaguegothic.woff2') format('woff2'),
url('fonts/leaguegothic.woff') format('woff');
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Cabin';
src: url('fonts/cabin-regular.woff2') format('woff2'),
url('fonts/cabin-regular.woff') format('woff');
font-weight: normal;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Cabin';
src: url('fonts/cabin-italic.woff2') format('woff2'),
url('fonts/cabin-italic.woff') format('woff');
font-style: italic;
font-display: swap;
}
@font-face {
font-family: 'Cabin';
src: url('fonts/cabin-bold.woff2') format('woff2'),
url('fonts/cabin-bold.woff') format('woff');
font-weight: bold;
font-style: normal;
font-display: swap;
}
:root {
--font-family-text: "Cabin", "Trebuchet MS", -apple-system, ".SFNSText-Regular", "San Francisco",
"Roboto", "Segoe UI", "Helvetica Neue", "Lucida Grande", Arial,
sans-serif;
--font-family-monospace: Monaco, Menlo, Consolas, "Lucida Console",
"Courier New", monospace;
--font-family-heading: "League Gothic", -apple-system, ".SFNSText-Regular", "San Francisco",
"Roboto", "Segoe UI", "Helvetica Neue", "Lucida Grande", Arial,
sans-serif;
--type-size-1: 4rem;
--type-size-2: 3.236rem;
--type-size-3: 2.618rem;
--type-size-4: 2rem;
--type-size-5: 1.618rem;
--type-size-6: 1.309rem;
--red: #e63c2f;
--blue: #15284b;
--light-blue: #93b7bb;
--beige: #e8d5d3;
--green: #2c9a42;
--dark-gray: color(var(--beige) blend(var(--blue) 50%));
--gray: color(var(--beige) blend(var(--blue) 25%));
--light-gray: color(var(--beige) blend(var(--blue) 12.5%));
--lighter-gray: var(--beige);
--lightest-gray: color(var(--beige) blend(#fff 75%));
--dark: var(--blue);
--light: var(--light-blue);
--sidebar-background: var(--beige);
--sidebar-link-active-background: var(--light-blue);
--sidebar-collapsible--hamburger-color: var(--beige);
--text: var(--blue);
--link-color: var(--red);
--brand-color: var(--blue);
--brand-background: var(--red);
--code-tag-background-color: var(--lightest-gray);
--code-tag-border-radius: 0;
--code-tag-box-shadow: none;
--homepage-navbar-background: var(--red);
--hero-button-block-background: var(--beige);
--homepage-hero-background: #fff;
--content-floating-blocks-background: var(--blue);
}
body {
line-height: 1.618;
}
a.Link--external::after {
content: '';
}
.s-content {
code {
display: inline-block;
padding-top: 0;
padding-bottom: 0;
padding: 0.5ch;
border: 0;
&::before, &::after {
content: '';
}
pre & {
display: inline;
}
}
table {
border-collapse: separate;
border-spacing: 2px;
border: 2px solid var(--gray);
thead, tbody {
background-color: #fff;
}
tr {
border-top: 0;
&:nth-child(2n) {
background-color: transparent;
td {
background-color: var(--lightest-gray);
}
}
}
th, td {
border: 0;
}
}
}
.s-content table, .Nav__item .Nav__item {
font-size: 1rem;
}
.Brand, h1, h2, h3, h4, h5, h6 {
font-weight: normal;
}
.Button {
border-radius: 0;
}
.HomepageButtons .Button--hero {
font-weight: normal;
font-size: var(--type-size-6);
}
.Page__header {
border-bottom: 2px solid var(--lighter-gray);
}
.Pager li > a {
border: 2px solid var(--lighter-gray);
border-radius: 0;
&:hover, &:focus {
background-color: var(--lighter-gray);
}
}
.Pager--prev a::before {
content: "\2190\00a0";
}
.Pager--next a::after {
content: "\00a0\2192";
}
.Navbar {
height: auto;
box-shadow: none;
.Brand {
float: none;
line-height: inherit;
height: auto;
}
}
.Homepage {
padding-top: 10px !important;
}
.Nav__item {
font-size: var(--type-size-6);
}
.Nav__arrow:before, .Nav .Nav .Nav__item a .Nav__arrow:before {
font-family: var(--font-family-heading);
width: 1ch;
height: 1ch;
}
.TableOfContentsContainer__title {
border-bottom: 4px solid var(--lighter-gray);
}
ul.TableOfContents {
border-left: 6px solid var(--lighter-gray);
}
.Columns__right--full .TableOfContentsContainer {
.TableOfContentsContainer__content > .TableOfContents {
border-right: 2px solid var(--lighter-gray);
}
a {
border-bottom: 1px solid var(--lighter-gray);
}
}
.clients {
thead tr:first-child th {
text-align: left;
&:first-child {
width: 15%;
}
&:nth-child(3) {
width: 50%;
text-align: center;
}
}
thead tr + tr th {
width: 12%;
text-align: center;
}
tbody td {
&:nth-child(3), &:nth-child(4), &:nth-child(5), &:nth-child(6) {
text-align: center;
}
&.Y {
color: var(--green);
}
&.N {
color: var(--red);
}
}
}
.hljs, .s-content pre {
background: var(--blue);
color: var(--beige);
}
.hljs {
display: block;
overflow-x: auto;
padding: 0.5em;
}
.hljs-emphasis {
font-style: italic;
}
.hljs-strong {
font-weight: bold;
}
.hljs-comment, .hljs-quote {
color: #978e9c;
}
/* Green */
.hljs-keyword, .hljs-selector-tag, .hljs-addition {
color: #acb39a;
}
/* Cyan */
.hljs-number, .hljs-string, .hljs-meta .hljs-meta-string, .hljs-literal, .hljs-doctag, .hljs-regexp {
color: var(--light-blue);
}
/* Blue */
.hljs-title, .hljs-section, .hljs-name, .hljs-selector-id, .hljs-selector-class {
color: #82b7e5;
}
/* Yellow */
.hljs-attribute, .hljs-attr, .hljs-variable, .hljs-template-variable, .hljs-class .hljs-title, .hljs-type {
color: #c5b031;
}
/* Orange */
.hljs-symbol, .hljs-bullet, .hljs-subst, .hljs-meta, .hljs-meta .hljs-keyword, .hljs-selector-attr, .hljs-selector-pseudo, .hljs-link {
color: #ea8031;
}
/* Red */
.hljs-built_in, .hljs-deletion {
color: var(--red);
}
.hljs-formula {
background: #686986;
}
@media (--viewport-large) {
.Columns__left {
border: 0;
}
}

View file

@ -4,21 +4,25 @@
* See LICENSE and AUTHORS files for details */
declare(strict_types=1);
namespace JKingWeb\Arsse;
abstract class AbstractException extends \Exception {
const CODES = [
public const CODES = [
"Exception.uncoded" => -1,
"Exception.unknown" => 10000,
"Exception.constantUnknown" => 10001,
"Exception.arrayEmpty" => 10002,
"ExceptionType.strictFailure" => 10011,
"ExceptionType.typeUnknown" => 10012,
"Exception.extMissing" => 10021,
"Lang/Exception.defaultFileMissing" => 10101,
"Lang/Exception.fileMissing" => 10102,
"Lang/Exception.fileUnreadable" => 10103,
"Lang/Exception.fileCorrupt" => 10104,
"Lang/Exception.stringMissing" => 10105,
"Lang/Exception.stringInvalid" => 10106,
"Lang/Exception.dataInvalid" => 10107,
"Db/Exception.extMissing" => 10201,
"Db/Exception.fileMissing" => 10202,
"Db/Exception.fileUnusable" => 10203,
@ -26,6 +30,7 @@ abstract class AbstractException extends \Exception {
"Db/Exception.fileUnwritable" => 10205,
"Db/Exception.fileUncreatable" => 10206,
"Db/Exception.fileCorrupt" => 10207,
"Db/Exception.connectionFailure" => 10208,
"Db/Exception.updateTooNew" => 10211,
"Db/Exception.updateManual" => 10212,
"Db/Exception.updateManualOnly" => 10213,
@ -42,6 +47,8 @@ abstract class AbstractException extends \Exception {
"Db/Exception.savepointInvalid" => 10226,
"Db/Exception.savepointStale" => 10227,
"Db/Exception.resultReused" => 10228,
"Db/ExceptionRetry.schemaChange" => 10229,
"Db/ExceptionInput.invalidValue" => 10230,
"Db/ExceptionInput.missing" => 10231,
"Db/ExceptionInput.whitespace" => 10232,
"Db/ExceptionInput.tooLong" => 10233,
@ -54,19 +61,26 @@ abstract class AbstractException extends \Exception {
"Db/ExceptionInput.circularDependence" => 10238,
"Db/ExceptionInput.subjectMissing" => 10239,
"Db/ExceptionTimeout.general" => 10241,
"Db/ExceptionTimeout.logicalLock" => 10241,
"Conf/Exception.fileMissing" => 10301,
"Conf/Exception.fileUnusable" => 10302,
"Conf/Exception.fileUnreadable" => 10303,
"Conf/Exception.fileUnwritable" => 10304,
"Conf/Exception.fileUncreatable" => 10305,
"Conf/Exception.fileCorrupt" => 10306,
"User/Exception.functionNotImplemented" => 10401,
"User/Exception.doesNotExist" => 10402,
"User/Exception.alreadyExists" => 10403,
"Conf/Exception.typeMismatch" => 10311,
"Conf/Exception.semanticMismatch" => 10312,
"Conf/Exception.ambiguousDefault" => 10313,
"User/Exception.authMissing" => 10411,
"User/Exception.authFailed" => 10412,
"User/ExceptionAuthz.notAuthorized" => 10421,
"User/ExceptionConflict.doesNotExist" => 10402,
"User/ExceptionConflict.alreadyExists" => 10403,
"User/ExceptionSession.invalid" => 10431,
"User/ExceptionInput.invalidTimezone" => 10441,
"User/ExceptionInput.invalidValue" => 10442,
"User/ExceptionInput.invalidNonZeroInteger" => 10443,
"User/ExceptionInput.invalidUsername" => 10444,
"Feed/Exception.internalError" => 10500,
"Feed/Exception.invalidCertificate" => 10501,
"Feed/Exception.invalidUrl" => 10502,
"Feed/Exception.maxRedirect" => 10503,
@ -74,14 +88,43 @@ abstract class AbstractException extends \Exception {
"Feed/Exception.timeout" => 10505,
"Feed/Exception.forbidden" => 10506,
"Feed/Exception.unauthorized" => 10507,
"Feed/Exception.transmissionError" => 10508,
"Feed/Exception.connectionFailed" => 10509,
"Feed/Exception.malformedXml" => 10511,
"Feed/Exception.xmlEntity" => 10512,
"Feed/Exception.subscriptionNotFound" => 10521,
"Feed/Exception.unsupportedFeedFormat" => 10522,
"ImportExport/Exception.fileMissing" => 10601,
"ImportExport/Exception.fileUnreadable" => 10603,
"ImportExport/Exception.fileUnwritable" => 10604,
"ImportExport/Exception.fileUncreatable" => 10605,
"ImportExport/Exception.invalidSyntax" => 10611,
"ImportExport/Exception.invalidSemantics" => 10612,
"ImportExport/Exception.invalidFolderName" => 10613,
"ImportExport/Exception.invalidFolderCopy" => 10614,
"ImportExport/Exception.invalidTagName" => 10615,
"Rule/Exception.invalidPattern" => 10701,
"Service/Exception.pidNotFile" => 10801,
"Service/Exception.pidDirMissing" => 10802,
"Service/Exception.pidDirUnresolvable" => 10803,
"Service/Exception.pidUnusable" => 10804,
"Service/Exception.pidUnreadable" => 10805,
"Service/Exception.pidUnwritable" => 10806,
"Service/Exception.pidUncreatable" => 10807,
"Service/Exception.pidCorrupt" => 10808,
"Service/Exception.pidDuplicate" => 10809,
"Service/Exception.pidLocked" => 10810,
"Service/Exception.pidInaccessible" => 10811,
"Service/Exception.forkFailed" => 10812,
];
public function __construct(string $msgID = "", $vars = null, \Throwable $e = null) {
if ($msgID=="") {
protected $symbol;
protected $params;
public function __construct(string $msgID = "", $vars = null, ?\Throwable $e = null) {
$this->symbol = $msgID;
$this->params = $vars ?? [];
if ($msgID === "") {
$msg = "Exception.unknown";
$code = 10000;
} else {
@ -93,8 +136,16 @@ abstract class AbstractException extends \Exception {
$code = self::CODES[$codeID];
$msg = "Exception.".str_replace("\\", "/", $class).".$msgID";
}
$msg = Arsse::$lang->msg($msg, $vars);
$msg = (Arsse::$lang ?? new Lang)->msg($msg, $vars);
}
parent::__construct($msg, $code, $e);
}
public function getSymbol(): string {
return $this->symbol;
}
public function getParams(): array {
return $this->params;
}
}

View file

@ -4,25 +4,59 @@
* See LICENSE and AUTHORS files for details */
declare(strict_types=1);
namespace JKingWeb\Arsse;
class Arsse {
const VERSION = "0.4.0";
public const VERSION = "0.10.5";
public const REQUIRED_EXTENSIONS = [
"intl", // as this extension is required to prepare formatted messages, its absence will throw a distinct English-only exception
"dom",
"filter",
"json", // part of the PHP core since version 8.0
"hash", // part of the PHP core since version 7.4
"simplexml", // required by PicoFeed only
"iconv", // required by PicoFeed only
];
/** @var Lang */
/** @var Factory|\Phake\IMock */
public static $obj;
/** @var Lang|\Phake\IMock */
public static $lang;
/** @var Conf */
/** @var Conf|\Phake\IMock */
public static $conf;
/** @var Database */
/** @var Database|\Phake\IMock */
public static $db;
/** @var User */
/** @var User|\Phake\IMock */
public static $user;
public static function load(Conf $conf) {
static::$lang = new Lang();
/** @codeCoverageIgnore */
public static function bootstrap(): void {
$conf = file_exists(BASE."config.php") ? new Conf(BASE."config.php") : new Conf;
static::load($conf);
}
public static function load(Conf $conf): void {
static::$obj = static::$obj ?? new Factory;
static::$lang = static::$lang ?? new Lang;
static::$conf = $conf;
static::$lang->set($conf->lang);
static::$db = new Database();
static::$user = new User();
static::$db = static::$db ?? new Database;
static::$user = static::$user ?? new User;
}
/** Checks whether the specified extensions are loaded and throws an exception if any are not */
public static function checkExtensions(string ...$ext): void {
$missing = [];
foreach ($ext as $e) {
if (!extension_loaded($e)) {
$missing[] = $e;
}
}
if ($missing) {
$total = sizeof($missing);
$first = $missing[0];
throw new Exception("extMissing", ['first' => $first, 'total' => $total]);
}
}
}

View file

@ -4,89 +4,247 @@
* See LICENSE and AUTHORS files for details */
declare(strict_types=1);
namespace JKingWeb\Arsse;
use JKingWeb\Arsse\REST\Fever\User as Fever;
use JKingWeb\Arsse\ImportExport\OPML;
use JKingWeb\Arsse\REST\Miniflux\Token as Miniflux;
use JKingWeb\Arsse\Service\Daemon;
class CLI {
protected $args = [];
protected function usage(): string {
$prog = basename($_SERVER['argv'][0]);
return <<<USAGE_TEXT
public const USAGE = <<<USAGE_TEXT
Usage:
$prog daemon
$prog feed refresh <n>
$prog conf save-defaults <file>
$prog user add <username> [<password>]
$prog --version
$prog --help | -h
arsse.php user [list]
arsse.php user add <username> [<password>] [--admin]
arsse.php user remove <username>
arsse.php user show <username>
arsse.php user set <username> <property> <value>
arsse.php user unset <username> <property>
arsse.php user set-pass <username> [<password>] [--fever]
arsse.php user unset-pass <username> [--fever]
arsse.php user auth <username> <password> [--fever]
arsse.php token list <username>
arsse.php token create <username> [<label>]
arsse.php token revoke <username> [<token>]
arsse.php import <username> [<file>] [-f|--flat] [-r|--replace]
arsse.php export <username> [<file>] [-f|--flat]
arsse.php daemon [--fork=PIDFILE]
arsse.php feed refresh-all
arsse.php feed refresh <n>
arsse.php conf save-defaults [<file>]
arsse.php --version
arsse.php -h|--help
The Arsse command-line interface currently allows you to start the refresh
daemon, refresh a specific feed by numeric ID, add a user, or save default
configuration to a sample file.
The Arsse command-line interface can be used to perform various administrative
tasks such as starting the newsfeed refresh service, managing users, and
importing or exporting data.
See the manual page for more details:
man arsse
USAGE_TEXT;
protected function usage($prog): string {
$prog = basename($prog);
return str_replace("arsse.php", $prog, self::USAGE);
}
public function __construct(array $argv = null) {
$argv = $argv ?? array_slice($_SERVER['argv'], 1);
$this->args = \Docopt::handle($this->usage(), [
'argv' => $argv,
'help' => true,
'version' => Arsse::VERSION,
]);
}
protected function loadConf(): bool {
$conf = file_exists(BASE."config.php") ? new Conf(BASE."config.php") : new Conf;
Arsse::load($conf);
// command-line operations will never respect authorization
Arsse::$user->authorizationEnabled(false);
return true;
}
public function dispatch(array $args = null): int {
// act on command line
$args = $args ?? $this->args;
if ($this->command("daemon", $args)) {
$this->loadConf();
return $this->daemon();
} elseif ($this->command("feed refresh", $args)) {
$this->loadConf();
return $this->feedRefresh((int) $args['<n>']);
} elseif ($this->command("conf save-defaults", $args)) {
return $this->confSaveDefaults($args['<file>']);
} elseif ($this->command("user add", $args)) {
$this->loadConf();
return $this->userAdd($args['<username>'], $args['<password>']);
}
}
protected function command($cmd, $args): bool {
foreach (explode(" ", $cmd) as $part) {
if (!$args[$part]) {
return false;
protected function command($args): string {
$out = [];
foreach ($args as $k => $v) {
if (preg_match("/^[a-z]/", $k) && $v === true) {
$out[] = $k;
}
}
return implode(" ", $out);
}
/** @codeCoverageIgnore */
protected function loadConf(): bool {
Arsse::bootstrap();
return true;
}
public function daemon(bool $loop = true): int {
(new Service)->watch($loop);
return 0; // FIXME: should return the exception code of thrown exceptions
protected function resolveFile($file, string $mode): string {
// TODO: checking read/write permissions on the provided path may be useful
$stdinOrStdout = in_array($mode, ["r", "r+"]) ? "php://input" : "php://output";
return ($file === "-" ? null : $file) ?? $stdinOrStdout;
}
public function feedRefresh(int $id): int {
return (int) !Arsse::$db->feedUpdate($id); // FIXME: exception error codes should be returned here
public function dispatch(?array $argv = null): int {
$argv = $argv ?? $_SERVER['argv'];
$argv0 = array_shift($argv);
$args = \Docopt::handle($this->usage($argv0), [
'argv' => $argv,
'help' => false,
]);
try {
// ensure the require extensions are loaded
Arsse::checkExtensions(...Arsse::REQUIRED_EXTENSIONS);
// reconstitute multi-token commands (e.g. user add) into a single string
$cmd = $this->command($args);
if ($cmd && !in_array($cmd, ["", "conf save-defaults", "daemon"])) {
// only certain commands don't require configuration to be loaded; daemon loads configuration after forking (if applicable)
$this->loadConf();
}
// run the requested command
switch ($cmd) {
case "":
if ($args['--version']) {
echo Arsse::VERSION.\PHP_EOL;
} elseif ($args['--help'] || $args['-h']) {
echo $this->usage($argv0).\PHP_EOL;
}
return 0;
case "daemon":
if ($args['--fork'] !== null) {
return $this->serviceFork($args['--fork']);
} else {
$this->loadConf();
Arsse::$obj->get(Service::class)->watch(true);
}
return 0;
case "feed refresh":
return (int) !Arsse::$db->feedUpdate((int) $args['<n>'], true);
case "feed refresh-all":
Arsse::$obj->get(Service::class)->watch(false);
return 0;
case "conf save-defaults":
$file = $this->resolveFile($args['<file>'], "w");
return (int) !Arsse::$obj->get(Conf::class)->exportFile($file, true);
case "export":
$u = $args['<username>'];
$file = $this->resolveFile($args['<file>'], "w");
return (int) !Arsse::$obj->get(OPML::class)->exportFile($file, $u, ($args['--flat'] || $args['-f']));
case "import":
$u = $args['<username>'];
$file = $this->resolveFile($args['<file>'], "r");
return (int) !Arsse::$obj->get(OPML::class)->importFile($file, $u, ($args['--flat'] || $args['-f']), ($args['--replace'] || $args['-r']));
case "token list":
case "list token": // command reconstruction yields this order for "token list" command
return $this->tokenList($args['<username>']);
case "token create":
echo Arsse::$obj->get(Miniflux::class)->tokenGenerate($args['<username>'], $args['<label>']).\PHP_EOL;
return 0;
case "token revoke":
Arsse::$db->tokenRevoke($args['<username>'], "miniflux.login", $args['<token>']);
return 0;
case "user add":
$out = $this->userAddOrSetPassword("add", $args["<username>"], $args["<password>"]);
if ($args['--admin']) {
Arsse::$user->propertiesSet($args["<username>"], ['admin' => true]);
}
return $out;
case "user set-pass":
if ($args['--fever']) {
$passwd = Arsse::$obj->get(Fever::class)->register($args["<username>"], $args["<password>"]);
if (is_null($args["<password>"])) {
echo $passwd.\PHP_EOL;
}
return 0;
} else {
return $this->userAddOrSetPassword("passwordSet", $args["<username>"], $args["<password>"]);
}
// no break
case "user unset-pass":
if ($args['--fever']) {
Arsse::$obj->get(Fever::class)->unregister($args["<username>"]);
} else {
Arsse::$user->passwordUnset($args["<username>"]);
}
return 0;
case "user remove":
return (int) !Arsse::$user->remove($args["<username>"]);
case "user show":
return $this->userShowProperties($args["<username>"]);
case "user set":
return (int) !Arsse::$user->propertiesSet($args["<username>"], [$args["<property>"] => $args["<value>"]]);
case "user unset":
return (int) !Arsse::$user->propertiesSet($args["<username>"], [$args["<property>"] => null]);
case "user auth":
return $this->userAuthenticate($args["<username>"], $args["<password>"], $args["--fever"]);
case "user list":
case "user":
return $this->userList();
default:
throw new Exception("constantUnknown", $cmd); // @codeCoverageIgnore
}
} catch (AbstractException $e) {
$this->logError($e->getMessage());
return $e->getCode();
}
} // @codeCoverageIgnore
/** @codeCoverageIgnore */
protected function logError(string $msg): void {
fwrite(STDERR, $msg.\PHP_EOL);
}
public function confSaveDefaults(string $file): int {
return (int) !(new Conf)->exportFile($file, true);
protected function serviceFork(string $pidfile): int {
// initialize the object factory
Arsse::$obj = Arsse::$obj ?? new Factory;
// create a Daemon object which contains various helper functions
$daemon = Arsse::$obj->get(Daemon::class);
// resolve the PID file to its absolute path; this also checks its readability and writability
$pidfile = $daemon->checkPIDFilePath($pidfile);
// daemonize
$daemon->fork($pidfile);
// start the fetching service as normal
$this->loadConf();
Arsse::$obj->get(Service::class)->watch(true);
// after the service has been shut down, delete the PID file and exit cleanly
unlink($pidfile);
return 0;
}
public function userAdd(string $user, string $password = null): int {
$passwd = Arsse::$user->add($user, $password);
protected function userAddOrSetPassword(string $method, string $user, ?string $password = null, ?string $oldpass = null): int {
$passwd = Arsse::$user->$method(...array_slice(func_get_args(), 1));
if (is_null($password)) {
echo $passwd.\PHP_EOL;
}
return 0;
}
protected function userList(): int {
$list = Arsse::$user->list();
if ($list) {
echo implode(\PHP_EOL, $list).\PHP_EOL;
}
return 0;
}
protected function userAuthenticate(string $user, string $password, bool $fever = false): int {
$result = $fever ? Arsse::$obj->get(Fever::class)->authenticate($user, $password) : Arsse::$user->auth($user, $password);
if ($result) {
echo Arsse::$lang->msg("CLI.Auth.Success").\PHP_EOL;
return 0;
} else {
echo Arsse::$lang->msg("CLI.Auth.Failure").\PHP_EOL;
return 1;
}
}
protected function userShowProperties(string $user): int {
$data = Arsse::$user->propertiesGet($user);
$len = array_reduce(array_keys($data), function($carry, $item) {
return max($carry, strlen($item));
}, 0) + 2;
foreach ($data as $k => $v) {
echo str_pad($k, $len, " ");
echo var_export($v, true).\PHP_EOL;
}
return 0;
}
protected function tokenList(string $user): int {
$list = Arsse::$obj->get(Miniflux::class)->tokenList($user);
usort($list, function($v1, $v2) {
return $v1['label'] <=> $v2['label'];
});
foreach ($list as $t) {
echo $t['id']." ".$t['label'].\PHP_EOL;
}
return 0;
}
}

View file

@ -5,96 +5,142 @@
/** Conf class */
declare(strict_types=1);
namespace JKingWeb\Arsse;
use AllowDynamicProperties;
use JKingWeb\Arsse\Misc\ValueInfo as Value;
/** Class for loading, saving, and querying configuration
*
* The Conf class serves both as a means of importing and querying configuration information, as well as a source for default parameters when a configuration file does not specify a value.
* All public properties are configuration parameters that may be set by the server administrator. */
#[AllowDynamicProperties]
class Conf {
/** @var string Default language to use for logging and errors */
public $lang = "en";
public $lang = "en";
/** @var string Class of the database driver in use (SQLite3 by default) */
public $dbDriver = Db\SQLite3\Driver::class;
/** @var boolean Whether to attempt to automatically update the database when updated to a new version with schema changes */
public $dbAutoUpdate = true;
/** @var string The database driver to use, one of "sqlite3", "postgresql", or "mysql". A fully-qualified class name may also be used for custom drivers */
public $dbDriver = "sqlite3";
/** @var boolean Whether to attempt to automatically update the database when upgrading to a new version with schema changes */
public $dbAutoUpdate = true;
/** @var \DateInterval|null Number of seconds to wait before returning a timeout error when connecting to a database (null waits forever; not applicable to SQLite) */
public $dbTimeoutConnect = 5.0;
/** @var \DateInterval|null Number of seconds to wait before returning a timeout error when executing a database operation (null waits forever; not applicable to SQLite) */
public $dbTimeoutExec = null;
/** @var \DateInterval|null Number of seconds to wait before returning a timeout error when acquiring a database lock (null waits forever) */
public $dbTimeoutLock = 60.0;
/** @var string|null Full path and file name of SQLite database (if using SQLite) */
public $dbSQLite3File = null;
public $dbSQLite3File = null;
/** @var string Encryption key to use for SQLite database (if using a version of SQLite with SEE) */
public $dbSQLite3Key = "";
/** @var integer Number of seconds for SQLite to wait before returning a timeout error when writing to the database */
public $dbSQLite3Timeout = 60;
public $dbSQLite3Key = "";
/** @var string Host name, address, or socket path of PostgreSQL database server (if using PostgreSQL) */
public $dbPostgreSQLHost = "";
/** @var string Log-in user name for PostgreSQL database server (if using PostgreSQL) */
public $dbPostgreSQLUser = "arsse";
/** @var string Log-in password for PostgreSQL database server (if using PostgreSQL) */
public $dbPostgreSQLPass = "";
/** @var integer Listening port for PostgreSQL database server (if using PostgreSQL over TCP) */
public $dbPostgreSQLPort = 5432;
/** @var string Database name on PostgreSQL database server (if using PostgreSQL) */
public $dbPostgreSQLDb = "arsse";
/** @var string Schema name in PostgreSQL database (if using PostgreSQL) */
public $dbPostgreSQLSchema = "";
/** @var string Service file entry to use (if using PostgreSQL); if using a service entry all above parameters except schema are ignored */
public $dbPostgreSQLService = "";
/** @var string Host name or address of MySQL database server (if using MySQL) */
public $dbMySQLHost = "localhost";
/** @var string Log-in user name for MySQL database server (if using MySQL) */
public $dbMySQLUser = "arsse";
/** @var string Log-in password for MySQL database server (if using MySQL) */
public $dbMySQLPass = "";
/** @var integer Listening port for MySQL database server (if using MySQL over TCP) */
public $dbMySQLPort = 3306;
/** @var string Database name on MySQL database server (if using MySQL) */
public $dbMySQLDb = "arsse";
/** @var string Unix domain socket or named pipe to use for MySQL when not connecting over TCP */
public $dbMySQLSocket = "";
/** @var string Class of the user management driver in use (Internal by default) */
public $userDriver = User\Internal\Driver::class;
/** @var string The user management driver to use, currently only "internal". A fully-qualified class name may also be used for custom drivers */
public $userDriver = "internal";
/** @var boolean Whether users are already authenticated by the Web server before the application is executed */
public $userPreAuth = false;
public $userPreAuth = false;
/** @var boolean Whether to require successful HTTP authentication before processing API-level authentication for protocols which have any. Normally the Tiny Tiny RSS relies on its own session-token authentication scheme, for example */
public $userHTTPAuthRequired = false;
public $userHTTPAuthRequired = false;
/** @var integer Desired length of temporary user passwords */
public $userTempPasswordLength = 20;
public $userTempPasswordLength = 20;
/** @var boolean Whether invalid or expired API session tokens should prevent logging in when HTTP authentication is used, for protocol which implement their own authentication */
public $userSessionEnforced = true;
/** @var string Period of inactivity after which log-in sessions should be considered invalid, as an ISO 8601 duration (default: 24 hours)
public $userSessionEnforced = true;
/** @var \DateInterval Period of inactivity after which log-in sessions should be considered invalid, as an ISO 8601 duration (default: 24 hours)
* @see https://en.wikipedia.org/wiki/ISO_8601#Durations */
public $userSessionTimeout = "PT24H";
/** @var string Maximum lifetime of log-in sessions regardless of activity, as an ISO 8601 duration (default: 7 days);
public $userSessionTimeout = "PT24H";
/** @var \DateInterval Maximum lifetime of log-in sessions regardless of activity, as an ISO 8601 duration (default: 7 days);
* @see https://en.wikipedia.org/wiki/ISO_8601#Durations */
public $userSessionLifetime = "P7D";
public $userSessionLifetime = "P7D";
/** @var string Class of the background feed update service driver in use (Forking by default) */
public $serviceDriver = Service\Forking\Driver::class;
/** @var string The interval between checks for new articles, as an ISO 8601 duration
/** @var string Feed update service driver to use, one of "serial" or "subprocess". A fully-qualified class name may also be used for custom drivers */
public $serviceDriver = "subprocess";
/** @var \DateInterval The interval between checks for new articles, as an ISO 8601 duration
* @see https://en.wikipedia.org/wiki/ISO_8601#Durations */
public $serviceFrequency = "PT2M";
public $serviceFrequency = "PT2M";
/** @var integer Number of concurrent feed updates to perform */
public $serviceQueueWidth = 5;
/** @var string The base server address (with scheme, host, port if necessary, and terminal slash) to connect to the server when performing feed updates using cURL */
public $serviceCurlBase = "http://localhost/";
/** @var string The user name to use when performing feed updates using cURL; if none is provided, a temporary name and password will be stored in the database (this is not compatible with pre-authentication) */
public $serviceCurlUser = null;
/** @var string The password to use when performing feed updates using cURL */
public $serviceCurlPassword = null;
public $serviceQueueWidth = 5;
/** @var integer Number of seconds to wait for data when fetching feeds from foreign servers */
public $fetchTimeout = 10;
/** @var \DateInterval Number of seconds to wait for data when fetching feeds from foreign servers */
public $fetchTimeout = 10.0;
/** @var integer Maximum size, in bytes, of data when fetching feeds from foreign servers */
public $fetchSizeLimit = 2 * 1024 * 1024;
public $fetchSizeLimit = 2 * 1024 * 1024;
/** @var boolean Whether to allow the possibility of fetching full article contents using an item's URL. Whether fetching will actually happen is also governed by a per-feed setting */
public $fetchEnableScraping = true;
public $fetchEnableScraping = true;
/** @var string|null User-Agent string to use when fetching feeds from foreign servers */
public $fetchUserAgentString;
public $fetchUserAgentString = null;
/** @var string When to delete a feed from the database after all its subscriptions have been deleted, as an ISO 8601 duration (default: 24 hours; empty string for never)
/** @var \DateInterval|null When to delete a feed from the database after all its subscriptions have been deleted, as an ISO 8601 duration (default: 24 hours; null for never)
* @see https://en.wikipedia.org/wiki/ISO_8601#Durations */
public $purgeFeeds = "PT24H";
/** @var string When to delete an unstarred article in the database after it has been marked read by all users, as an ISO 8601 duration (default: 7 days; empty string for never)
public $purgeFeeds = "PT24H";
/** @var \DateInterval|null When to delete an unstarred article in the database after it has been marked read by all users, as an ISO 8601 duration (default: 7 days; null for never)
* @see https://en.wikipedia.org/wiki/ISO_8601#Durations */
public $purgeArticlesRead = "P7D";
/** @var string When to delete an unstarred article in the database regardless of its read state, as an ISO 8601 duration (default: 21 days; empty string for never)
public $purgeArticlesRead = "P7D";
/** @var \DateInterval|null When to delete an unstarred article in the database regardless of its read state, as an ISO 8601 duration (default: 21 days; null for never)
* @see https://en.wikipedia.org/wiki/ISO_8601#Durations */
public $purgeArticlesUnread = "P21D";
public $purgeArticlesUnread = "P21D";
/** @var string Application name to present to clients during authentication */
public $httpRealm = "The Advanced RSS Environment";
public $httpRealm = "The Advanced RSS Environment";
/** @var string Space-separated list of origins from which to allow cross-origin resource sharing */
public $httpOriginsAllowed = "*";
public $httpOriginsAllowed = "*";
/** @var string Space-separated list of origins from which to deny cross-origin resource sharing */
public $httpOriginsDenied = "";
public $httpOriginsDenied = "";
### OBSOLETE SETTINGS
/** @var \DateInterval|null (OBSOLETE) Number of seconds for SQLite to wait before returning a timeout error when trying to acquire a write lock on the database (zero does not wait) */
public $dbSQLite3Timeout = null; // previously 60.0
protected const EXPECTED_TYPES = [
'dbTimeoutExec' => "double",
'dbTimeoutLock' => "double",
'dbTimeoutConnect' => "double",
'dbSQLite3Timeout' => "double",
];
protected $types = [];
/** Creates a new configuration object
* @param string $import_file Optional file to read configuration data from
* @see self::importFile() */
public function __construct(string $import_file = "") {
if ($import_file != "") {
$this->types = $this->propertyDiscover();
foreach (array_keys($this->types) as $prop) {
$this->$prop = $this->propertyImport($prop, $this->$prop);
}
if ($import_file !== "") {
$this->importFile($import_file);
}
}
/** Layers configuration data from a file into an existing object
*
* The file must be a PHP script which return an array with keys that match the properties of the Conf class. Malformed files will throw an exception; unknown keys are silently ignored. Files may be imported is succession, though this is not currently used.
* The file must be a PHP script which returns an array with keys that match the properties of the Conf class. Malformed files will throw an exception; unknown keys are silently accepted. Files may be imported in succession, though this is not currently used.
* @param string $file Full path and file name for the file to import */
public function importFile(string $file): self {
if (!file_exists($file)) {
@ -113,16 +159,22 @@ class Conf {
if (!is_array($arr)) {
throw new Conf\Exception("fileCorrupt", $file);
}
return $this->import($arr);
return $this->importData($arr, $file);
}
/** Layers configuration data from an associative array into an existing object
*
* The input array must have keys that match the properties of the Conf class; unknown keys are silently ignored. Arrays may be imported is succession, though this is not currently used.
* The input array must have keys that match the properties of the Conf class; unknown keys are silently accepted. Arrays may be imported in succession, though this is not currently used.
* @param mixed[] $arr Array of configuration parameters to export */
public function import(array $arr): self {
$file = debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS, 1)[0]['file'] ?? "";
return $this->importData($arr, $file);
}
/** Layers configuration data from an associative array into an existing object */
protected function importData(array $arr, string $file): self {
foreach ($arr as $key => $value) {
$this->$key = $value;
$this->$key = $this->propertyImport($key, $value, $file);
}
return $this;
}
@ -130,17 +182,23 @@ class Conf {
/** Outputs configuration settings, either non-default ones or all, as an associative array
* @param bool $full Whether to output all configuration options rather than only changed ones */
public function export(bool $full = false): array {
$ref = new self;
$out = [];
$conf = new \ReflectionObject($this);
$ref = (new \ReflectionClass($this))->getDefaultProperties();
$out = [];
foreach ($conf->getProperties(\ReflectionProperty::IS_PUBLIC) as $prop) {
$name = $prop->name;
// add the property to the output if the value is scalar (or null) and either:
// 1. full output has been requested
// 2. the property is not defined in the class
// 3. it differs from the default
if ((is_scalar($this->$name) || is_null($this->$name)) && ($full || !$prop->isDefault() || $this->$name !== $ref->$name)) {
$out[$name] = $this->$name;
$value = $prop->getValue($this);
if ($prop->isDefault()) {
$default = $ref[$name];
// if the property is a known property (rather than one added by a hypothetical plug-in)
// we convert intervals to strings and then export anything which doesn't match the default value
$value = $this->propertyExport($name, $value);
if ((is_scalar($value) || is_null($value)) && ($full || $value !== $ref[$name])) {
$out[$name] = $value;
}
} elseif (is_scalar($value) || is_null($value)) {
// otherwise export the property only if it is scalar
$out[$name] = $value;
}
}
return $out;
@ -159,13 +217,11 @@ class Conf {
// retrieve the property's docblock, if it exists
try {
$doc = (new \ReflectionProperty(self::class, $prop))->getDocComment();
} catch (\ReflectionException $e) {
}
if ($doc) {
// parse the docblock to extract the property description
if (preg_match("<@var\s+\S+\s+(.+?)(?:\s*\*/)?$>m", $doc, $match)) {
if (preg_match("<@var\s+\S+\s+(.+?)(?:\s*\*/)?\s*$>m", $doc, $match)) {
$comment = $match[1];
}
} catch (\ReflectionException $e) {
}
// append the docblock description if there is one, or an empty comment otherwise
$out .= " // ".$comment.PHP_EOL;
@ -181,4 +237,97 @@ class Conf {
}
return true;
}
/** Caches information about configuration properties for later access */
protected function propertyDiscover(): array {
$out = [];
$rc = new \ReflectionClass($this);
foreach ($rc->getProperties(\ReflectionProperty::IS_PUBLIC) as $p) {
if (preg_match("/@var\s+((?:int(eger)?|float|bool(ean)?|string|\\\\DateInterval)(?:\|null)?)[^\[]/", $p->getDocComment(), $match)) {
$match = explode("|", $match[1]);
$nullable = (sizeof($match) > 1);
$type = [
'string' => Value::T_STRING | Value::M_STRICT,
'integer' => Value::T_INT | Value::M_STRICT,
'boolean' => Value::T_BOOL | Value::M_STRICT,
'float' => Value::T_FLOAT | Value::M_STRICT,
'\\DateInterval' => Value::T_INTERVAL | Value::M_LOOSE,
][$match[0]];
if ($nullable) {
$type |= Value::M_NULL;
}
} else {
// catch-all for custom properties
$type = Value::T_MIXED; // @codeCoverageIgnore
}
$out[$p->name] = ['name' => $match[0], 'const' => $type];
}
return $out;
}
protected function propertyImport(string $key, $value, string $file = "") {
$typeName = $this->types[$key]['name'] ?? "mixed";
$typeConst = $this->types[$key]['const'] ?? Value::T_MIXED;
$nullable = (int) (bool) ($typeConst & Value::M_NULL);
try {
if ($typeName === "\\DateInterval") {
// date intervals have special handling: if the existing value (ultimately, the default value)
// is an integer or float, the new value should be imported as numeric. If the new value is a string
// it is first converted to an interval and then converted to the numeric type if necessary
$mode = $nullable ? Value::M_STRICT | Value::M_NULL : Value::M_STRICT;
if (is_string($value)) {
$value = Value::normalize($value, Value::T_INTERVAL | $mode);
}
switch (self::EXPECTED_TYPES[$key] ?? gettype($this->$key)) {
case "integer":
// no properties are currently typed as integers
return Value::normalize($value, Value::T_INT | $mode); // @codeCoverageIgnore
case "double":
return Value::normalize($value, Value::T_FLOAT | $mode);
case "string":
case "object":
return $value;
default:
// this should never occur
throw new Conf\Exception("ambiguousDefault", ['param' => $key]); // @codeCoverageIgnore
}
}
$value = Value::normalize($value, $typeConst);
switch ($key) {
case "dbDriver":
$driver = $driver ?? Database::DRIVER_NAMES[strtolower($value)] ?? $value;
$interface = $interface ?? Db\Driver::class;
// no break
case "userDriver":
$driver = $driver ?? User::DRIVER_NAMES[strtolower($value)] ?? $value;
$interface = $interface ?? User\Driver::class;
// no break
case "serviceDriver":
$driver = $driver ?? Service::DRIVER_NAMES[strtolower($value)] ?? $value;
$interface = $interface ?? Service\Driver::class;
if (!is_subclass_of($driver, $interface)) {
throw new Conf\Exception("semanticMismatch", ['param' => $key, 'file' => $file]);
}
return $driver;
}
return $value;
} catch (ExceptionType $e) {
$type = $this->types[$key]['const'] & ~(Value::M_STRICT | Value::M_DROP | Value::M_NULL | Value::M_ARRAY);
throw new Conf\Exception("typeMismatch", ['param' => $key, 'type' => Value::TYPE_NAMES[$type], 'file' => $file, 'nullable' => $nullable]);
}
}
protected function propertyExport(string $key, $value) {
$value = ($value instanceof \DateInterval) ? Value::normalize($value, Value::T_STRING) : $value;
switch ($key) {
case "dbDriver":
return array_flip(Database::DRIVER_NAMES)[$value] ?? $value;
case "userDriver":
return array_flip(User::DRIVER_NAMES)[$value] ?? $value;
case "serviceDriver":
return array_flip(Service::DRIVER_NAMES)[$value] ?? $value;
default:
return $value;
}
}
}

View file

@ -4,6 +4,7 @@
* See LICENSE and AUTHORS files for details */
declare(strict_types=1);
namespace JKingWeb\Arsse\Conf;
class Exception extends \JKingWeb\Arsse\AbstractException {

View file

@ -0,0 +1,28 @@
<?php
/** @license MIT
* Copyright 2017 J. King, Dustin Wilson et al.
* See LICENSE and AUTHORS files for details */
declare(strict_types=1);
namespace JKingWeb\Arsse\Context;
abstract class AbstractContext {
protected $props = [];
protected $parent = null;
protected function act(string $prop, int $set, $value) {
if ($set) {
if (is_null($value)) {
unset($this->props[$prop]);
$this->$prop = (new \ReflectionClass($this))->getDefaultProperties()[$prop];
} else {
$this->props[$prop] = true;
$this->$prop = $value;
}
return $this->parent ?? $this;
} else {
return isset($this->props[$prop]);
}
}
}

View file

@ -0,0 +1,36 @@
<?php
/** @license MIT
* Copyright 2017 J. King, Dustin Wilson et al.
* See LICENSE and AUTHORS files for details */
declare(strict_types=1);
namespace JKingWeb\Arsse\Context;
trait BooleanMembers {
public $unread = null;
public $starred = null;
public $hidden = null;
public $labelled = null;
public $annotated = null;
public function unread(?bool $spec = null) {
return $this->act(__FUNCTION__, func_num_args(), $spec);
}
public function starred(?bool $spec = null) {
return $this->act(__FUNCTION__, func_num_args(), $spec);
}
public function hidden(?bool $spec = null) {
return $this->act(__FUNCTION__, func_num_args(), $spec);
}
public function labelled(?bool $spec = null) {
return $this->act(__FUNCTION__, func_num_args(), $spec);
}
public function annotated(?bool $spec = null) {
return $this->act(__FUNCTION__, func_num_args(), $spec);
}
}

30
lib/Context/Context.php Normal file
View file

@ -0,0 +1,30 @@
<?php
/** @license MIT
* Copyright 2017 J. King, Dustin Wilson et al.
* See LICENSE and AUTHORS files for details */
declare(strict_types=1);
namespace JKingWeb\Arsse\Context;
class Context extends RootContext {
use BooleanMembers;
use ExclusionMembers;
/** @var ExclusionContext */
public $not;
public function __construct() {
$this->not = new ExclusionContext($this);
}
public function __clone() {
// clone the exclusion context as well
$this->not = clone $this->not;
}
/** @codeCoverageIgnore */
public function __destruct() {
unset($this->not);
}
}

View file

@ -0,0 +1,31 @@
<?php
/** @license MIT
* Copyright 2017 J. King, Dustin Wilson et al.
* See LICENSE and AUTHORS files for details */
declare(strict_types=1);
namespace JKingWeb\Arsse\Context;
class ExclusionContext extends AbstractContext {
use ExclusionMembers;
public function __construct(?Context $parent = null) {
$this->parent = $parent;
}
public function __clone() {
// if the context was cloned because its parent was cloned, change the parent to the clone
if ($this->parent) {
$t = debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS | \DEBUG_BACKTRACE_PROVIDE_OBJECT, 2)[1];
if (($t['object'] ?? null) instanceof Context && $t['function'] === "__clone") {
$this->parent = $t['object'];
}
}
}
/** @codeCoverageIgnore */
public function __destruct() {
unset($this->parent);
}
}

View file

@ -0,0 +1,262 @@
<?php
/** @license MIT
* Copyright 2017 J. King, Dustin Wilson et al.
* See LICENSE and AUTHORS files for details */
declare(strict_types=1);
namespace JKingWeb\Arsse\Context;
use JKingWeb\Arsse\Misc\ValueInfo;
use JKingWeb\Arsse\Misc\Date;
trait ExclusionMembers {
public $folder = null;
public $folders = [];
public $folderShallow = null;
public $foldersShallow = [];
public $tag = null;
public $tags = [];
public $tagName = null;
public $tagNames = [];
public $subscription = null;
public $subscriptions = [];
public $edition = null;
public $editions = [];
public $article = null;
public $articles = [];
public $label = null;
public $labels = [];
public $labelName = null;
public $labelNames = [];
public $annotationTerms = [];
public $searchTerms = [];
public $titleTerms = [];
public $authorTerms = [];
public $articleRange = [null, null];
public $editionRange = [null, null];
public $modifiedRange = [null, null];
public $modifiedRanges = [];
public $markedRange = [null, null];
public $markedRanges = [];
protected function cleanIdArray(array $spec, bool $allowZero = false): array {
$spec = array_values($spec);
for ($a = 0; $a < sizeof($spec); $a++) {
if (ValueInfo::id($spec[$a], $allowZero)) {
$spec[$a] = (int) $spec[$a];
} else {
$spec[$a] = null;
}
}
return array_values(array_unique(array_filter($spec, function($v) {
return !is_null($v);
})));
}
protected function cleanStringArray(array $spec): array {
$spec = array_values($spec);
$stop = sizeof($spec);
for ($a = 0; $a < $stop; $a++) {
if (strlen($str = ValueInfo::normalize($spec[$a], ValueInfo::T_STRING | ValueInfo::M_DROP) ?? "")) {
$spec[$a] = $str;
} else {
unset($spec[$a]);
}
}
return array_values(array_unique($spec));
}
protected function cleanDateRangeArray(array $spec): array {
$spec = array_values($spec);
$stop = sizeof($spec);
for ($a = 0; $a < $stop; $a++) {
if (!is_array($spec[$a]) || sizeof($spec[$a]) !== 2) {
unset($spec[$a]);
} else {
$spec[$a] = ValueInfo::normalize($spec[$a], ValueInfo::T_DATE | ValueInfo::M_ARRAY | ValueInfo::M_DROP);
if ($spec[$a] === [null, null]) {
unset($spec[$a]);
}
}
}
return array_values(array_unique($spec, \SORT_REGULAR));
}
public function folder(?int $spec = null) {
return $this->act(__FUNCTION__, func_num_args(), $spec);
}
public function folders(?array $spec = null) {
if (isset($spec)) {
$spec = $this->cleanIdArray($spec, true);
}
return $this->act(__FUNCTION__, func_num_args(), $spec);
}
public function folderShallow(?int $spec = null) {
return $this->act(__FUNCTION__, func_num_args(), $spec);
}
public function foldersShallow(?array $spec = null) {
if (isset($spec)) {
$spec = $this->cleanIdArray($spec, true);
}
return $this->act(__FUNCTION__, func_num_args(), $spec);
}
public function tag(?int $spec = null) {
return $this->act(__FUNCTION__, func_num_args(), $spec);
}
public function tags(?array $spec = null) {
if (isset($spec)) {
$spec = $this->cleanIdArray($spec);
}
return $this->act(__FUNCTION__, func_num_args(), $spec);
}
public function tagName(?string $spec = null) {
return $this->act(__FUNCTION__, func_num_args(), $spec);
}
public function tagNames(?array $spec = null) {
if (isset($spec)) {
$spec = $this->cleanStringArray($spec);
}
return $this->act(__FUNCTION__, func_num_args(), $spec);
}
public function subscription(?int $spec = null) {
return $this->act(__FUNCTION__, func_num_args(), $spec);
}
public function subscriptions(?array $spec = null) {
if (isset($spec)) {
$spec = $this->cleanIdArray($spec);
}
return $this->act(__FUNCTION__, func_num_args(), $spec);
}
public function edition(?int $spec = null) {
return $this->act(__FUNCTION__, func_num_args(), $spec);
}
public function article(?int $spec = null) {
return $this->act(__FUNCTION__, func_num_args(), $spec);
}
public function editions(?array $spec = null) {
if (isset($spec)) {
$spec = $this->cleanIdArray($spec);
}
return $this->act(__FUNCTION__, func_num_args(), $spec);
}
public function articles(?array $spec = null) {
if (isset($spec)) {
$spec = $this->cleanIdArray($spec);
}
return $this->act(__FUNCTION__, func_num_args(), $spec);
}
public function label(?int $spec = null) {
return $this->act(__FUNCTION__, func_num_args(), $spec);
}
public function labels(?array $spec = null) {
if (isset($spec)) {
$spec = $this->cleanIdArray($spec);
}
return $this->act(__FUNCTION__, func_num_args(), $spec);
}
public function labelName(?string $spec = null) {
return $this->act(__FUNCTION__, func_num_args(), $spec);
}
public function labelNames(?array $spec = null) {
if (isset($spec)) {
$spec = $this->cleanStringArray($spec);
}
return $this->act(__FUNCTION__, func_num_args(), $spec);
}
public function annotationTerms(?array $spec = null) {
if (isset($spec)) {
$spec = $this->cleanStringArray($spec);
}
return $this->act(__FUNCTION__, func_num_args(), $spec);
}
public function searchTerms(?array $spec = null) {
if (isset($spec)) {
$spec = $this->cleanStringArray($spec);
}
return $this->act(__FUNCTION__, func_num_args(), $spec);
}
public function titleTerms(?array $spec = null) {
if (isset($spec)) {
$spec = $this->cleanStringArray($spec);
}
return $this->act(__FUNCTION__, func_num_args(), $spec);
}
public function authorTerms(?array $spec = null) {
if (isset($spec)) {
$spec = $this->cleanStringArray($spec);
}
return $this->act(__FUNCTION__, func_num_args(), $spec);
}
public function articleRange(?int $start = null, ?int $end = null) {
if ($start === null && $end === null) {
$spec = null;
} else {
$spec = [$start, $end];
}
return $this->act(__FUNCTION__, func_num_args(), $spec);
}
public function editionRange(?int $start = null, ?int $end = null) {
if ($start === null && $end === null) {
$spec = null;
} else {
$spec = [$start, $end];
}
return $this->act(__FUNCTION__, func_num_args(), $spec);
}
public function modifiedRange($start = null, $end = null) {
if ($start === null && $end === null) {
$spec = null;
} else {
$spec = [Date::normalize($start), Date::normalize($end)];
}
return $this->act(__FUNCTION__, func_num_args(), $spec);
}
public function modifiedRanges(?array $spec = null) {
if (isset($spec)) {
$spec = $this->cleanDateRangeArray($spec);
}
return $this->act(__FUNCTION__, func_num_args(), $spec);
}
public function markedRange($start = null, $end = null) {
if ($start === null && $end === null) {
$spec = null;
} else {
$spec = [Date::normalize($start), Date::normalize($end)];
}
return $this->act(__FUNCTION__, func_num_args(), $spec);
}
public function markedRanges(?array $spec = null) {
if (isset($spec)) {
$spec = $this->cleanDateRangeArray($spec);
}
return $this->act(__FUNCTION__, func_num_args(), $spec);
}
}

View file

@ -0,0 +1,21 @@
<?php
/** @license MIT
* Copyright 2017 J. King, Dustin Wilson et al.
* See LICENSE and AUTHORS files for details */
declare(strict_types=1);
namespace JKingWeb\Arsse\Context;
abstract class RootContext extends AbstractContext {
public $limit = 0;
public $offset = 0;
public function limit(?int $spec = null) {
return $this->act(__FUNCTION__, func_num_args(), $spec);
}
public function offset(?int $spec = null) {
return $this->act(__FUNCTION__, func_num_args(), $spec);
}
}

View file

@ -0,0 +1,51 @@
<?php
/** @license MIT
* Copyright 2017 J. King, Dustin Wilson et al.
* See LICENSE and AUTHORS files for details */
declare(strict_types=1);
namespace JKingWeb\Arsse\Context;
class UnionContext extends RootContext implements \ArrayAccess, \Countable, \IteratorAggregate {
protected $contexts = [];
#[\ReturnTypeWillChange]
public function offsetExists($offset) {
return isset($this->contexts[$offset]);
}
#[\ReturnTypeWillChange]
public function offsetGet($offset) {
return $this->contexts[$offset] ?? null;
}
#[\ReturnTypeWillChange]
public function offsetSet($offset, $value) {
assert($value instanceof RootContext, new \Exception("Union contexts may only contain other non-exclusion contexts"));
if (isset($offset)) {
$this->contexts[$offset] = $value;
} else {
$this->contexts[] = $value;
}
}
#[\ReturnTypeWillChange]
public function offsetUnset($offset) {
unset($this->contexts[$offset]);
}
public function count(): int {
return count($this->contexts);
}
public function getIterator(): \Traversable {
foreach ($this->contexts as $k => $c) {
yield $k => $c;
}
}
public function __construct(RootContext ...$context) {
$this->contexts = $context;
}
}

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