* Fix auto-share shortcut on first use
- Re-read the share actions after creating the lazy-loaded share menu.
- Restore the documented one-key share behavior when exactly one action is configured.
## Why
The shortcut checked the action count before it built the menu. On first use it therefore found no action and required a second press.
Fixes#7299.
* Slight variable scope adjustment
---------
Co-authored-by: Gerard Alvear <gerard.alvear@logiqd.me>
Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
## Problem
With OIDC on the official Docker image, interactive SSO logins intermittently return `HTTP 400` at the `/i/oidc/` callback. The failure tracks login duration. `mod_auth_openidc` sets an anti-CSRF state cookie bounded by `OIDCStateTimeout` (default 300 s). If a login through an external IdP takes longer than that (MFA, a slow provider screen, the user stepping away), the cookie is already gone when the callback returns. The image does not set `OIDCDefaultURL`, so the module has no fallback and returns an error instead of restarting the login.
Apache log:
```
oidc_response_proto_state_restore: state has expired
oidc_response_process: invalid authorization response state and
no default SSO URL is set, sending an error...
→ GET /i/oidc/?code=…&state=… 400
```
This is [documented mod_auth_openidc behaviour](72c9f479c2/auth_openidc.conf (L723-L733)) when `OIDCDefaultURL` is unset. Both `OIDCDefaultURL` and `OIDCStateTimeout` are `RSRC_CONF` (server config only), so the `./FreshRSS/p/i/.htaccess` escape hatch cannot set them and the fix has to live in the image.
Reproduce: log out, start SSO, wait more than 5 minutes on the IdP screen, complete.
## Fix
Add `OIDC_DEFAULT_URL`, wired through the existing `${OIDC_*}` env pattern, defaulting to `/i/` (the index, consistent with `OIDCRedirectURI /i/oidc/`). On an expired or unmatchable state the module now redirects to `/i/`, which is protected and starts a fresh login against the existing IdP session. `OIDCStateTimeout` stays at 300 s; the window is fine, the missing fallback was the bug.
`OIDC_STATE_TIMEOUT` is intentionally not exposed. It can be added the same way if there is a reason to tune the window.
* Docker/OIDC: redirect to a default URL on expired auth state instead of 400-ing
Adds OIDC_DEFAULT_URL (default /i/), wired through the existing ${OIDC_*} env
pattern, so an expired or unmatchable mod_auth_openidc state redirects to the
FreshRSS index and restarts the login instead of returning HTTP 400.
* git commit update
---------
Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
* Add link to cloud providers list and Chatons directory
Closes#7628
Changes proposed in this pull request:
* Added a line after the "Automated install" section linking to the cloud providers list (freshrss.org/cloud-providers.html) and the Chatons directory
* Follows Alkarex's suggestion in the issue (keep the section, add links instead of removing content)
* docs(fr): add link to cloud providers list and Chatons directory
* Typographic quotes
---------
Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
* Clarify archiving/purge documentation
Explain purge policy priority (feed > category > global), the discovery-date based purge logic, and that purging is only triggered on a random subset of feed refreshes (or via Purge now / cli/purge.php).
Fixes#6863.
Co-Authored-By: Claude <noreply@anthropic.com>
* Typographic quotes
* No changelog for (minor) documentation
---------
Co-authored-by: Gerard Alvear <gerard.alvear@logiqd.me>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
* Preserve negative timezone offsets in date intervals
## Summary
- Preserve the sign of ISO 8601 timezone offsets while normalizing date intervals.
- Add regression coverage for a negative offset.
## Why
The previous normalization removed every hyphen, turning `-05:00` into `0500` and parsing the timestamp in the wrong timezone.
* Pad dates before timezone offsets
* Minor test comment and format
---------
Co-authored-by: Gerard Alvear <gerard.alvear@logiqd.me>
Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
* Complete two source TODOs: negative UTC offset date parsing and PostgreSQL tag insert
_noDelimit() stripped '-' and ':' from the whole ISO 8601 string, including a
trailing UTC offset, so a negative offset such as "-05:00" silently lost its
sign and corrupted the parsed date. Extract a trailing offset (or "Z") before
stripping delimiters and re-append it unchanged.
FreshRSS_TagDAOPGSQL::sqlIgnore() returned an empty string as a placeholder,
so re-tagging an already-tagged entry produced a real SQL error on PostgreSQL
instead of being a silent no-op like on MySQL/SQLite. Replace the sqlIgnore()
keyword-injection approach with the sqlIgnoreConflict(string $sql): string
pattern already used by EntryDAO, and implement it for PostgreSQL with
"ON CONFLICT DO NOTHING".
Fixes#7923.
Co-Authored-By: Claude <noreply@anthropic.com>
* Drop the negative-UTC-offset date fix, already covered by #9071#9071 already fixes this exact bug in lib_date.php, so keeping this
PR focused on the PostgreSQL tag-insert fix only.
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Gerard Alvear <gerard.alvear@logiqd.me>
Co-authored-by: Claude <noreply@anthropic.com>
* Move article header/footer options from Reading to Display configuration
Feed title, authors and date, article icons position, and tags options
control the article header/footer layout, so they belong next to the
existing top/bottom line icon settings on the Display configuration
page rather than on Reading.
Fixes#6866.
Co-Authored-By: Claude <noreply@anthropic.com>
* Move section further down
---------
Co-authored-by: Gerard Alvear <gerard.alvear@logiqd.me>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
* AI skills: suggestion to limit the number of PRs
* review comments
* tweak phrasing
* Bump to 10
---------
Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
* Redirect away from wrong URL paths to fix displaying of relative URLs
* Also strip path info for API pages
* Error for API
---------
Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
On MySQL and MariaDB (the compressed content_bin code path,
isCompressed() === true), three of the four regex-search constructions
in FreshRSS_EntryDAO::sqlBooleanSearch() generated invalid SQL, so the
query failed and the search silently returned zero articles:
* intext:/regex/ appended a stray closing parenthesis after
sqlRegex(...) (MySQL error 1248);
* -intext:/regex/ had the same stray parenthesis;
* -/regex/ emitted ' ANT NOT ' instead of ' AND NOT '
(MySQL error 1064).
sqlRegex() already returns a self-contained, balanced expression
(REGEXP_LIKE(expr, ?, 'flags') or expr REGEXP ?), so no extra
parenthesis is needed, matching the uncompressed (SQLite / PostgreSQL)
branches.
The existing MySQL / MariaDB unit tests for intext:/regex/ had
captured the buggy unbalanced SQL as their expected strings; they are
corrected, and new cases are added for -intext:/regex/ and -/regex/
on both flavours.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* Support categories in feed add URLs
## Summary
- Honour the `category` query parameter when opening the add-feed form.
- Keep the existing `cat_id` parameter as a backwards-compatible fallback.
- Use `category` for the add-feed links in the subscription view.
This makes the URL proposed in #4160 select the intended category:
`?c=feed&a=add&category=1&url_rss=https://example.com/feed.xml`.
## Validation
- PHP syntax checks for the changed controller and templates.
- Focused request-parameter checks for explicit `category`, legacy `cat_id`, and the default category.
Fixes#4160
* Simplify and small fixes
---------
Co-authored-by: Gerard Alvear <gerard.alvear@logiqd.me>
Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
* Refresh only feeds in the current view
Closes#8025. Passes the current feed or category selection to the refresh endpoint and limits the batch accordingly.
* Fix case of Dynamic OPML, and keep feed order
---------
Co-authored-by: Gerard Alvear <gerard.alvear@logiqd.me>
Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
Closes#3662. Closes the extension settings slider when its reset/cancel control is used while preserving unsaved-change confirmation.
---------
Co-authored-by: Gerard Alvear <gerard.alvear@logiqd.me>
As requested in #9065, adding myself to the contributors list.
Inserted alphabetically. Contribution: #9065 (stop the unread-count poller after the session ends).
* Add per-feed option to show or hide enclosures
Some feeds attach lots of enclosures (images, audio, video, downloads)
that clutter the article view for readers who only want the text. Add
a per-feed "Show enclosures" setting (Feed configuration > Display),
defaulting to "by default" (i.e. shown, matching current behaviour),
following the same ternary-attribute pattern used by existing
per-feed overrides such as show_unread_count and read_upon_gone.
Also apply the preference to the GReader-compat API content call and
the Fever API entry serialization, so third-party clients like Reeder
and Fever apps respect it too, not just the normal web UI. Renamed the
translation key to display_enclosures to match the underlying feed
attribute name.
Fixes#4999
* i18n: fr
* Fix Fever API
* Remove accessor
* Forgotten fix
---------
Co-authored-by: Gerard Alvear <gerard.alvear@logiqd.me>
Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
* Show only one representation per media:group
A <media:group> gathers <media:content> elements that are effectively
the same content, yet different representations (e.g. several
resolutions of the same video), but every representation was stored and
rendered as its own enclosure. For instance, articles from PeerTube
feeds showed the same video five times.
When loading entries, keep only one <media:content> per <media:group>:
the one marked with isDefault="true", or the first one otherwise.
https://www.rssboard.org/media-rss#media-groupFixes#8569
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Update SimplePie to include FreshRSS/simplepie#86
Regenerate the vendored SimplePie from FreshRSS/simplepie@freshrss via
`composer update --no-autoloader`, replacing the hand-patched Item.php
with the merged upstream version.
Includes <https://github.com/FreshRSS/simplepie/pull/86>.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Remove redundant test
Already covered by https://github.com/FreshRSS/simplepie/pull/86
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
* Document OpenBSD httpd API configuration
Closes#7493. Adds an OpenBSD httpd example for FreshRSS with PATH_INFO preserved for the Google Reader API.
* Document caching considerations for OpenBSD httpd
* A few edits
---------
Co-authored-by: Gerard Alvear <gerard.alvear@logiqd.me>
Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
Closes#7209. Documents CURLOPT_HTTPPROXYTUNNEL with a data/config.php example for HTTPS feeds through HTTP CONNECT proxies.
Co-authored-by: Gerard Alvear <gerard.alvear@logiqd.me>
* Fix outdated username length hint on registration and install forms
The hint text said usernames were limited to 16 alphanumeric
characters, but FreshRSS_user_Controller::USERNAME_PATTERN has long
allowed 1-39 characters, including underscore, dot, @ and hyphen.
Update the English source strings so the hint matches what is
actually accepted, using make i18n-update-key so the change
propagates to languages that had an untranslated copy of the old
English text (en-US, he), keeping en-US's IGNORE marker intact.
39 is simply the pattern's technical ceiling, not a claim about what
is safest to allow; that broader question was never settled on the
issue and is left for a follow-up if needed.
Fixes#3971
* i18n: mark username hints dirty
* i18n: retain untranslated Hebrew username hint
* i18n: fr
* i18n: TODO instead of DIRTY
---------
Co-authored-by: Gerard Alvear <gerard.alvear@logiqd.me>
Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
* Add log search to the logs page
Closes#8471. Adds a search field and filters log level, timestamp, and message before pagination
* Fix log search formatting
* Type the log search view property
* Fix missing i18n
Another option could be `index.menu.search_short`
* Fix form submit
---------
Co-authored-by: Gerard Alvear <gerard.alvear@logiqd.me>
Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
## What changed
- derive the actualization mutex name from a SHA-256 hash of the canonical `DATA_PATH`
- keep `TMP_PATH` as the configurable location for the mutex file
- add coverage for deterministic per-instance names and independent locks
## Why
Multiple FreshRSS instances that use the default shared system temporary directory currently use the same actualization lock. The data path identifies the instance without exposing or depending on its configured salt.
## Validation
- `vendor/bin/phpunit --bootstrap ./tests/bootstrap.php ./tests/lib/ActualizeMutexTest.php --no-progress`
- `vendor/bin/phpcs app/actualize_script.php lib/lib_rss.php tests/lib/ActualizeMutexTest.php -s`
- `vendor/bin/phpstan analyse --memory-limit 512M --no-progress app/actualize_script.php lib/lib_rss.php tests/lib/ActualizeMutexTest.php`
Fixes#6370
Co-authored-by: Gerard Alvear <gerard.alvear@logiqd.me>
* fix: stop the unread poller after the session ends
refreshUnreads() polls nbUnreadsPerFeed every 2 minutes and never checks
the response status, so once the session ends it keeps polling a dead
session indefinitely. Each request is a 401 (auth proxy / OIDC image) or
403 (stock); behind fail2ban the repeated failures ban the client IP.
Handle it like the other badAjax(status == 403) call sites: on a 401/403,
reload to re-authenticate instead of retrying.
* Enlarge HTTP 4xx status range
---------
Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
* Check GMP during 32-bit installation
- add an installer prerequisite for GMP when PHP uses 32-bit integers
- stop installation before favourite export can call GMP functions that are unavailable
- cover 32-bit and 64-bit GMP requirement states
Google Reader-compatible entry identifiers require GMP on 32-bit PHP. The installer did not check that prerequisite, so a missing extension could instead cause a fatal error during favourite export.
* i18n: fr
---------
Co-authored-by: Gerard Alvear <gerard.alvear@logiqd.me>
Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
* Update Japanese admin translations
* Update Japanese API translations
* Update Japanese configuration translations
* Update Japanese feedback translations
* Update Japanese general translations
* Update Japanese index translations
* Update Japanese installer translations
* Update Japanese subscription translations
* Normalize punctuation in Japanese translations
* fix(i18n): Correct Japanese scraping item label
* fix(i18n): Improve Japanese translations
* fix(i18n): Refine Japanese translations
* fix(i18n): Clear resolved Japanese DIRTY markers
* fix(i18n): Resolve remaining Japanese DIRTY translations
* fix(i18n): Use Arabic numerals for Japanese months
* style(i18n): Fix Japanese translation indentation
* docs(credits): Add gshimo contributor
* make fix-all
---------
Co-authored-by: gshimo <gakushimodaila@gmail.com>
Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
* Add Nextcloud Bookmarks sharing service
Adds a new self-hosted sharing option that posts articles to a
Nextcloud instance's Bookmarks app bookmarklet endpoint, following
the same pattern as the existing Linkding and Shaarli entries.
Closes#8053
* i18n: use Nextcloud Bookmarks translations
* Update i18n
* Fix wrong search&replace
---------
Co-authored-by: Gerard Alvear <gerard.alvear@logiqd.me>
Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
* Use :user-invalid instead of :invalid for form field styling
Every theme styled required inputs and selects with :invalid, which
matches as soon as an empty required field renders, before the user
has even touched it. This made fields such as the username on the
"Add user" admin form show a red border on first load, with nothing
wrong yet.
Switch to :user-invalid, which only matches after the user has
interacted with the field (e.g. on blur or submit), so a field only
turns red once it is actually flagged as invalid by the user's own
input. Browser support for :user-invalid is now good enough across
evergreen browsers (~89%, per caniuse) for this to be safe, which
was the blocker that stopped a previous attempt at this fix in #3724.
Fixes#3647
* CSS: support legacy Gecko invalid styles
* Drop legacy -moz-ui-invalid
Not important enough
---------
Co-authored-by: Gerard Alvear <gerard.alvear@logiqd.me>
Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
* Add LinkAce as a sharing service
LinkAce is a self-hosted bookmark manager with a bookmarklet-style
add endpoint, similar to Linkding and Shaarli. Add a 'linkace' entry
to app/shares.php using its documented bookmarklet URL scheme, add
the corresponding translation key, and list it in the sharing
services documentation.
Fixes#6698
* i18n Ignore
---------
Co-authored-by: Gerard Alvear <gerard.alvear@logiqd.me>
Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
* Warn during install if document root does not point to ./p
If the Web server's document root is misconfigured to serve the
FreshRSS repository root instead of the ./p public folder, other
folders such as ./data (which store credentials, tokens, and the
database) can become publicly accessible.
Add a non-blocking check to installation step 1 that compares the
detected DOCUMENT_ROOT against the ./p folder and warns when they
do not match. The check is skipped when DOCUMENT_ROOT is unknown
(e.g. CLI/cron context), including when it is an empty string,
since realpath('') would otherwise misleadingly resolve to the
current working directory.
Fixes#3679
* i18n: fr, en-US
* Path semantics preference
* Minor syntax preference
* Warn when document root is unavailable
---------
Co-authored-by: Gerard Alvear <gerard.alvear@logiqd.me>
Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
The Docker Quick Run / cron examples use single-quoted `-e 'CRON_MIN=1,31'`. This works on Linux/macOS shells and PowerShell, but not on Windows `cmd.exe`, which does not strip single quotes from arguments — they end up included literally in the variable, so the cron job never gets scheduled correctly. Using `-e CRON_MIN=*/20` (no quotes) works, which was the original report.
Fixes#4818
* Docs: remove CRON_MIN quoting
---------
Co-authored-by: Gerard Alvear <gerard.alvear@logiqd.me>
`--db-prefix` used to be defined with an optional value (`::`), but PHP's getopt() only accepts a value for an optional-value long option via `--db-prefix=value`, never the documented `--db-prefix value` form.
Passing it space-separated leaves the value as an unrecognized argument and silently drops every flag that follows (e.g. --disable_update) from getopt()'s result.
Revert to a somewhat required value (`:`), which still allows an empty prefix via `--db-prefix ''`.
Closes https://github.com/NixOS/nixpkgs/issues/530367
Changes proposed in this pull request:
- deprecate `--db-prefix=test_` (nowhere documented this way) and allow `--db-prefix test_`
The original Fever API docs at feedafever.com/api are offline, and only
an archive.org snapshot remains linked from our Fever API page. Add a
Markdown mirror of that content (with Alkarex's HTML-to-Markdown
translation) directly into the docs so it is preserved even if the
archive.org copy disappears.
The mirror is a faithful copy of Alkarex's translation, including the
"API Public Beta" intro paragraphs, with only the heading levels
adjusted to fit this page.
Fixes#7331
Co-authored-by: Gerard Alvear <gerard.alvear@logiqd.me>
PHPMailer enables SMTPAutoTLS by default, meaning it opportunistically
upgrades a plain SMTP connection to STARTTLS whenever the server
advertises support for it, regardless of the 'secure' => '' setting
documented in config.default.php. Minz_Mailer never read or set
$mail->SMTPAutoTLS, so this behaviour could not be turned off, making
it impossible to talk to SMTP servers with a self-signed or otherwise
untrusted certificate (e.g. a mail server on an internal Docker
network) without importing a custom CA.
Add a new 'auto_tls' key to the 'smtp' config array, defaulting to
true to preserve current behaviour, and wire it through to
$mail->SMTPAutoTLS in Minz_Mailer. Document the new option.
Fixes#2997
Co-authored-by: Gerard Alvear <gerard.alvear@logiqd.me>
* Dispatch a JS event when an entry finishes being marked read/unread
Extensions that render their own read/unread indicator outside of the
main article list (e.g. a custom reading pane) had no way to know when
a mark read/unread AJAX request completed, so their own UI could get
stuck (e.g. a spinner never reset).
Dispatch a `freshrss:entryStateChange` event on `document` with the
entry id and new state once the toggle has been applied, following the
existing `freshrss:*` JavaScript event convention used elsewhere in
main.js.
Fixes#8862
* Minor preferences
---------
Co-authored-by: Gerard Alvear <gerard.alvear@logiqd.me>
Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
`document.createEvent('Event')` followed by `Event.initEvent()` is
deprecated (https://developer.mozilla.org/en-US/docs/Web/API/Event/initEvent).
Replace the two remaining usages in `main.js` with the standard
`new Event(name, { bubbles, cancelable })` constructor, matching the
pattern already used in `extra.js`. The `bubbles` and `cancelable`
flags are kept identical (both `true`) so dispatch behaviour is
unchanged.
Fixes#5152
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
**Change(s)**
- Add missing punctuation
- Add missing spaces in the date format
- Add some new translations
**File(s) changed**
`app/i18n/zh-TW/{admin,conf,gen,install,sub}.php`
`README.md`
`README.fr.md`
Labels (tags) were sorted by the database's raw byte/ASCII collation
via ORDER BY name, so accented or non-Latin label names sorted after
all ASCII names instead of near their base letter.
Apply the same FreshRSS_Context::localeCompare() sort used for
categories, feeds, and shares (#8985) to TagDAO::listTags(), fixing
the last remaining sub-item of this issue; the others were already
resolved by #6212.
Fixes#6211
Co-authored-by: Gerard Alvear <gerard.alvear@logiqd.me>