Commit Graph

7286 Commits

Author SHA1 Message Date
Jam Balaya
7744f1fed9 WebSub: ignore http/https scheme difference in Self URL comparison (#9005)
* WebSub: ignore http/https scheme difference in Self URL comparison

The PubSubHubbub endpoint logged 'Self URL does not match registered
canonical URL' whenever a feed's self URL differed from the registered
canonical URL only by http vs https (common with http->https redirects),
spamming the log. Compare the URLs ignoring the http/https scheme while
still requiring host/path/query to match exactly.

Closes #3087

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Stylistic preferences

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
2026-07-09 07:50:42 +02:00
Inverle
6908d998d4 Fix .dockerignore being ignored during build in CI (#9001)
For example:

f201061345/.dockerignore (L6)

`/docs/` is included in `.dockerignore`, but currently if you check `docker exec freshrss ls -la`, you'll notice `/docs/` is being listed despite that.

This is because we are using git context for the build instead of path context, see: https://github.com/docker/build-push-action#git-context

Compare the runs https://github.com/Inverle/FreshRSS/actions/runs/28884689769/job/85681744466 and https://github.com/Inverle/FreshRSS/actions/runs/28885427454/job/85684280287 by CTRL+F'ing for `.dockerignore` in the `Build and push Docker images` step.
In the second run, a line `#5 [internal] load .dockerignore` is printed, but not in the first run without `context: .`

Additionally, it can be confirmed this fix is working by checking the `ghcr.io/inverle/freshrss:docker-testing-alpine` image for the `/docs/` directory.
2026-07-08 20:46:35 +02:00
Cactus
0b018adfc2 Clariffied netnews wire podcast compatibility (#8991)
* Clariffied netnews wire podcast compatibility

Netnewswire is unable to play podcasts.

* Update Netnewswire status in README.fr.md

* Fix Unicode

---------

Co-authored-by: Inverle <inverle@proton.me>
Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
2026-07-07 21:10:53 +02:00
Jam Balaya
f201061345 Feeds tree: sort feed names with locale-aware collation (#8985)
## Problem

The feed **tree** (left sidebar) and the **feed-title view** (main body) order feeds differently for names with accents or symbols. As reported in #8300, a Spanish feed starting with `Ú` appears **last** in the tree, after every ASCII-named feed.

The tree is sorted in PHP by `FreshRSS_Category::sortFeeds()` with `strnatcasecmp()`, which compares raw UTF-8 **bytes** — so any name starting with a non-ASCII character (`0xC3…` for `Á`, `Ñ`, `Ú`, …) sorts after `A–Z`. The main view is sorted by the database according to its collation, which orders accented letters near their base letter — hence the mismatch (as @Alkarex noted in the issue).

Closes #8300.

## Change

`sortFeeds()` now compares with a `Collator` (`ext-intl`, already a hard requirement) for the user's language, falling back to the previous `strnatcasecmp()` if a collator cannot be created:

```php
$collator = \Collator::create(FreshRSS_Context::hasUserConf() ? FreshRSS_Context::userConf()->language : '');
```

Sorting a Spanish set, before / after:

```
old: banana, Manzana, nube, Zorro, Ábaco, Ñandú, Úlcera, árbol
new: Ábaco, árbol, banana, Manzana, nube, Ñandú, Úlcera, Zorro
```

This makes the tree order locale-aware and much closer to the main view. Exact parity with the main view still depends on the database collation (which is admin/DB-specific and cannot be reproduced generically in PHP), but the "accented names sort last" problem is gone.

* Feeds tree: sort feed names with locale-aware collation

The feed tree sorted names byte-wise with strnatcasecmp(), which places
names starting with a non-ASCII character (e.g. an accented capital such
as "U-acute") after every ASCII name. Use a Collator for the user's
language so accented and non-ASCII names sort near their base letter,
closer to the database collation used on the main feed-title view.

For https://github.com/FreshRSS/FreshRSS/issues/8300

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Guard Collator with class_exists() so intl stays optional

The intl extension is only a recommended (not required) dependency, but
sortFeeds() called \Collator::create() unconditionally when a user language
was set. On installs without intl the \Collator class does not exist, so the
call raised a fatal "Class \"Collator\" not found" — effectively promoting
intl from recommended to required.

Guard the call with class_exists('Collator'): when intl is absent, fall back
to the previous byte-wise strnatcasecmp() sort. Locale-aware ordering stays a
progressive enhancement and the listed requirements are unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Shorten the sortFeeds() collation comment per review

Trim the rationale comment to a concise four lines as suggested in review,
keeping the locale-aware/fallback and class_exists() reasoning.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Stylistic preference

* Factorise localeCompare

* Minor code preference

* Preserve natural locale sort behaviour

* Avoid repeated locale lookup in comparator

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
2026-07-07 15:42:12 +02:00
Jam Balaya
ce7a196034 Remove unused simple layout (#8998)
Closes #6223.
Supersedes the investigation draft in #6765.

## Summary
- Removed the remaining explicit uses of the `simple` layout from the email-validation flows.
- Deleted `app/layout/simple.phtml` so those pages now use the shared `layout.phtml`.
- Kept the existing `disable_aside` behavior for the profile page while an email validation token is pending.

## Notes
The replacement for `simple.phtml` is the normal shared layout. Access while email validation is pending is still constrained by the existing `FreshRSS::checkEmailValidated()` route guard, which allows the validation/profile/logout paths and forwards other routes to `user#validateEmail`.
2026-07-07 15:04:11 +02:00
Jam Balaya
5014915c40 Fix article hover date visibility (#8997)
- Keep the article title in its own grid area on hover when the article row also shows a date.
- Restore a small spacing between the hovered title and the date.
- Regenerate the RTL stylesheet counterpart.

Fixes #8994

## Screenshots

![FreshRSS #8994 hover date comparison](https://raw.githubusercontent.com/JamBalaya56562/FreshRSS/assets-8994-hover-date/docs/screenshots/freshrss-8994-hover-date-comparison.png)

The comparison uses a synthetic hover fixture with FreshRSS article-row DOM and theme CSS, so no local feed data is included.
2026-07-07 13:59:25 +02:00
i @man
2188070aa6 Add option to keep or reset article sort order when navigating (#8969)
* Add option to keep or reset article sort order when navigating

Adds a new per-user reading option, "Keep the current sort order when
navigating between categories and feeds" (config key: sticky_sort), under
Settings > Reading > "Left navigation: Categories". It defaults to enabled,
which reproduces the current behaviour exactly.

Background (addresses #8953)
----------------------------
Once a sort/order was active in one view, it was propagated to every sidebar
link and to the "mark all as read" redirect. Opening another category then
ignored that category's own defaultSort/defaultOrder, because FreshRSS_Context
only applies those defaults when sort/order are absent from the request. As a
result the current view's sort (whether chosen manually or resolved from that
view's default) "bled" into the next category/feed.

Behaviour of the new option
---------------------------
- ON (default): unchanged. The current sort/order stays sticky while
  navigating, exactly as before.
- OFF: sidebar links and the "mark all as read" redirect no longer carry
  sort/order, so each target resolves its own order in FreshRSS_Context:
  feed/category defaultSort/defaultOrder, falling back to the global Reading
  sort criterion when none is configured.

order/sort propagation across the UI (for reference)
----------------------------------------------------
- Categories & feeds (sidebar): now gated by sticky_sort (app/layout/aside_feed.phtml).
- "Mark all as read" -> next category, both the top navigation button and the
  stream-footer button: now gated by sticky_sort
  (app/Controllers/entryController.php::readAction, non-AJAX redirect).
- Custom user queries: unaffected. A query's link is a self-contained URL built
  from that query's own saved parameters, so switching query A -> B never
  carried A's sort; no option is needed there.
- Stream transition links when sorting by category/feed name: intentionally
  left unchanged (out of scope; the sort is intrinsic to that grouping).

Compatibility
-------------
- Not a breaking change: the default (true) reproduces the previous behaviour.
- No database migration. The setting is a standard user configuration value
  with its default declared in config-user.default.php, so existing users keep
  the previous behaviour transparently.
- The read/unread state filter still persists across navigation regardless of
  this option.

i18n
----
English strings authored; en-US marked as IGNORE. Translations for the other
25 languages were produced with an automated (AI) translation service and
should be reviewed by native speakers.

* Update app/i18n/en/conf.php

Co-authored-by: Frans de Jonge <fransdejonge@gmail.com>

* Comments preference

* Update app/i18n/en/conf.php

Co-authored-by: Frans de Jonge <fransdejonge@gmail.com>

* i18n updates and comment

* Update translations to match reworded sticky_sort strings

Re-applies the 24 non-English/French translations of the "sticky_sort"
reading option after the English and French wording was updated to
"Keep manual sort order during navigation".

* improve Dutch

* i18n DIRTY

* i18n en: custom sort order

---------

Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
Co-authored-by: Frans de Jonge <fransdejonge@gmail.com>
2026-07-07 11:04:31 +02:00
Jam Balaya
271257be42 Fix statistics repartition averages (#8996)
* Fix statistics repartition averages

* Minor code preference

---------

Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
2026-07-07 10:42:53 +02:00
Jam Balaya
310bcb5e90 Add read status hook for entries (#8995)
- Add a new `EntriesRead` / `entries_read` hook for extensions.
- Trigger the hook when `markRead()` changes entry read/unread state for explicit entry IDs.
- Cover both the default DAO path and the SQLite/PGSQL DAO path.
- Add Minz hook tests for the new hook signature and argument passing.

## Why

Extensions can already react to favorite/bookmark changes through `EntriesFavorite`. This adds the matching read/unread hook requested in #4051 for extensions that need to collect stats or react to manual read state changes.

Closes #4051
2026-07-07 09:04:32 +02:00
Inverle
cb13091b37 Fix GH issue links with label filter in docs (#8993)
The "good first issue" and "help wanted" links under https://freshrss.github.io/FreshRSS/en/contributing.html#fix-a-bug aren't working.
2026-07-06 13:22:52 +02:00
Jam Balaya
241c7fb425 Respect simplepie_syslog_enabled for cache-hit debug logs (#8986)
Two "uses cache" LOG_DEBUG syslog() calls were emitted on every feed
refresh regardless of the simplepie_syslog_enabled setting, so users who
disabled it still saw "FreshRSS SimplePie uses cache for ..." spam in
syslog (issue #8540). Guard both with simplepie_syslog_enabled, like the
neighbouring SimplePie GET logs already are. Real error and fatal logs
are left untouched.

For https://github.com/FreshRSS/FreshRSS/issues/8540

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 11:19:33 +02:00
Inverle
c3d0bd5b55 Clarify in docs favicons/ should not be skipped during backup (#8992)
Alternative for https://github.com/FreshRSS/FreshRSS/pull/8972, at least until the migration system is improved to support rollbacks.
2026-07-06 11:16:56 +02:00
Frans de Jonge
612ac4f117 Notifications: pluralize the "new articles" count message (#8988)
* Generate new articles notification string in PHP

Closes #6470

Changes proposed in this pull request:

- Generate notifications string  in PHP
- Use plurals support

* New articles notification: switch to plural forms

* README status

* fix paramInt invocation

* i18n

* make fix-all

* Apply suggestion from @Frenzie

---------

Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
2026-07-06 11:16:29 +02:00
utkutibet
56d31c53de Improve turkish translation 3 (#8989)
* Translate remaining Turkish strings

* Translate remaining Turkish strings

---------

Co-authored-by: Utku Tibet <utkutibet@Utkus-MacBook-Pro.local>
2026-07-05 14:20:15 +02:00
Jam Balaya
ffc5a966c3 Migrate markdownlint-cli to markdownlint-cli2 (single config file) (#8987)
* Migrate markdownlint-cli to markdownlint-cli2

Replace markdownlint-cli with markdownlint-cli2 and consolidate the three
separate configuration points (.markdownlint.json rules, .markdownlintignore
excludes, and the '**/*.md' glob in the npm scripts) into a single
.markdownlint-cli2.jsonc holding config + globs + ignores.

The npm script names (markdownlint / markdownlint_fix) are unchanged, so the
CI step and the test/fix compound scripts need no change. markdownlint-cli2
v0.23.0 bundles the same markdownlint v0.41.0, so linting behaviour is
identical (112 files, 0 errors).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Add JSONC to .editorconfig

* Reduce obvious comments

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
2026-07-05 14:18:49 +02:00
Jam Balaya
cf2ceafca1 OPML: include each feed's refresh interval (TTL) in export/import (#8982)
Feeds moved between instances via OPML lost their refresh interval,
forcing manual reconfiguration. Add a 'frss:ttl' attribute (signed, so a
negative value also carries the muted state) to the OPML export, and read
it back on import. Documented in docs/en/developers/OPML.md.

For https://github.com/FreshRSS/FreshRSS/issues/5914

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 21:55:13 +02:00
Jam Balaya
988c678634 Fever API: log the client IP address on authentication failure (#8981)
A failed Fever API auth returns {"auth":0} with HTTP 200, so from the web
server log a failure is indistinguishable from a success and fail2ban
cannot act. The failure was already logged to API_LOG, but without the
client IP. Append the remote IP (Minz_Request::connectionRemoteAddress())
so a fail2ban filter watching data/users/_/log_api.txt can ban repeated
attempts.

For https://github.com/FreshRSS/FreshRSS/issues/6814

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 21:33:09 +02:00
Jam Balaya
6351a71938 cli/health.php: output a diagnostic when the health check fails (#8980)
Previously the Docker healthcheck script exited with a bare die(1) /
die(2) and no message, so users only saw 'unhealthy' with no clue why.
Print an informative reason to STDERR before each failing exit (a
connection/HTTP error, or a response that is not a FreshRSS page),
without changing the exit codes relied on by Docker HEALTHCHECK.

For https://github.com/FreshRSS/FreshRSS/issues/8026

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 18:59:30 +02:00
Jam Balaya
c90e48fd62 Report new articles count per feed in actualize_script output (#8948)
* feat: report new articles count per feed in actualize_script output

Closes #8291

## Changes proposed in this pull request

`app/actualize_script.php` now reports, for each feed that received new articles, how many new articles were fetched — in addition to the existing per-user summary. This makes batch refreshes (cron / systemd timers) much more informative.

Example output:

```
FreshRSS actualize admin…
	10 new article(s) from feed: FreshRSS releases
	3 new article(s) from feed: Some other blog
```

The feature is implemented **entirely within the CLI script**, reusing the existing extension-hook mechanism that the script already uses for the actualization mutex — no change to `feedController` or to any other behaviour:

- `FeedBeforeActualize` records each feed's name.
- `EntryBeforeAdd` counts new entries per feed. This hook fires only in the "genuinely new GUID" branch of the actualization loop (updated articles go through `EntryBeforeUpdate`), so the count matches the controller's own `$nbNewArticles` counter. Because the counting hook is registered after `$app->init()`, it runs last in the chain and is skipped for entries dropped by another extension — i.e. it counts only articles that are actually added.

Counts are printed through the script's existing `notice()` helper, so they reach STDOUT regardless of the `environment` setting (unlike `Minz_Log::notice`, which is suppressed in `production`).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Minor

* Remove tab
The tab character is escaped in some logs, e.g.

```
 [notice] --- \x091 new article(s) from feed: L’Express
```

* Sort per-feed output and also report updated articles

Addresses review feedback on #8948:
- Sort the per-feed report by descending article count, then feed name.
- Also count updated articles (via EntryBeforeUpdate) and report them
  alongside new ones, e.g. '10 new, 2 updated article(s) from feed: X'.
  EntryBeforeAdd fires only for new articles (new-GUID branch), so
  updated articles need the separate EntryBeforeUpdate hook.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Reduced code

* Do not report feeds with no new or updated articles

FeedBeforeActualize initializes every actualized feed in $feedStatistics
and fires before the TTL/mute skips, so skipped or unchanged feeds would
otherwise print an empty count ('  article(s) from feed: X'). Skip them.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Minor code logic preference

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
2026-07-04 17:38:40 +02:00
Inverle
13c2b68806 Keep scroll position of slider after form submit (#8974)
Follow-up of https://github.com/FreshRSS/FreshRSS/pull/8612#issuecomment-4078560064

Depends on https://github.com/FreshRSS/FreshRSS/pull/8973
2026-07-04 13:32:52 +02:00
Harry Andreolas
44b5c132b8 i18n: Update el (Greek) translations (#8977)
* Update el (i18n)

* Update CREDITS.md
2026-07-04 12:26:51 +02:00
utkutibet
da52ac4ee0 Translated remaining Turkish strings (#8970)
* Translate remaining Turkish strings

* Trigger CI re-run

* Update translation status

---------

Co-authored-by: Utku Tibet <utkutibet@Utkus-MacBook-Pro.local>
Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
2026-07-04 12:22:20 +02:00
Inverle
9b43639c88 Fix slider not reinitializing after form submit (#8973)
"Follow-up" of https://github.com/FreshRSS/FreshRSS/pull/8612 (not fixing a regression, just a bug)

Some features like URL observers and custom favicon JS (e.g. *Reset to default* button) wouldn't work, due to `extra.js` not being loaded properly on pages where the slider content was already included in the HTML.
2026-07-04 12:14:05 +02:00
Inverle
8a5f7d24b6 Remove .DS_Store files and update .gitignore (#8971)
The `.DS_Store` files were accidentally added in #8966
2026-07-03 13:28:36 +02:00
utkutibet
2e6e571b91 Worked on Turkish translation (#8966)
* Worked on Turkish translation

* Add my name to CREDITS.md

* Run make fix-all

* Fix credits

---------

Co-authored-by: Utku Tibet <utkutibet@Utkus-MacBook-Pro.local>
Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
2026-07-03 10:03:55 +02:00
Alexandre Alapetite
339d5c7ef1 Minor Minz_Extension typing (#8952)
Declare some accessors as non-empty-string, to make some extension code slightly nicer, e.g. https://github.com/FreshRSS/Extensions/pull/470
2026-07-02 15:09:07 +02:00
Inverle
a3badc2d2e Fix bad header filtering bypass (#8964)
* Fix bad header filtering bypass

* Sanitize every save using `_attribute('curl_params')`

* Sanitize every read using `attributeArray('curl_params')`
2026-07-02 15:08:32 +02:00
dependabot[bot]
c90c86a5c0 chore(deps-dev): bump phpstan/phpstan-phpunit from 2.0.16 to 2.0.17 (#8960)
Bumps [phpstan/phpstan-phpunit](https://github.com/phpstan/phpstan-phpunit) from 2.0.16 to 2.0.17.
- [Release notes](https://github.com/phpstan/phpstan-phpunit/releases)
- [Commits](https://github.com/phpstan/phpstan-phpunit/compare/2.0.16...2.0.17)

---
updated-dependencies:
- dependency-name: phpstan/phpstan-phpunit
  dependency-version: 2.0.17
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
2026-07-01 21:38:02 +02:00
dependabot[bot]
93e83943a3 chore(deps-dev): bump phpstan/phpstan from 2.2.2 to 2.2.3 (#8962)
* chore(deps-dev): bump phpstan/phpstan from 2.2.2 to 2.2.3

Bumps [phpstan/phpstan](https://github.com/phpstan/phpstan-phar-composer-source) from 2.2.2 to 2.2.3.
- [Commits](https://github.com/phpstan/phpstan-phar-composer-source/commits)

---
updated-dependencies:
- dependency-name: phpstan/phpstan
  dependency-version: 2.2.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* Ignore a could of redundant tests, for keeping additional safety

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
2026-07-01 21:35:39 +02:00
dependabot[bot]
abd683e8a0 chore(deps-dev): bump the stylelint group with 2 updates (#8958)
Bumps the stylelint group with 2 updates: [stylelint](https://github.com/stylelint/stylelint) and [@stylistic/stylelint-plugin](https://github.com/stylelint-stylistic/stylelint-stylistic).


Updates `stylelint` from 17.12.0 to 17.14.0
- [Release notes](https://github.com/stylelint/stylelint/releases)
- [Changelog](https://github.com/stylelint/stylelint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/stylelint/stylelint/compare/17.12.0...17.14.0)

Updates `@stylistic/stylelint-plugin` from 5.2.0 to 5.2.1
- [Release notes](https://github.com/stylelint-stylistic/stylelint-stylistic/releases)
- [Changelog](https://github.com/stylelint-stylistic/stylelint-stylistic/blob/main/CHANGELOG.md)
- [Commits](https://github.com/stylelint-stylistic/stylelint-stylistic/compare/v5.2.0...v5.2.1)

---
updated-dependencies:
- dependency-name: stylelint
  dependency-version: 17.14.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: stylelint
- dependency-name: "@stylistic/stylelint-plugin"
  dependency-version: 5.2.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: stylelint
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-01 20:58:01 +02:00
dependabot[bot]
e8bcb4021d chore(deps): bump ruby/setup-ruby from 1.310.0 to 1.315.0 (#8959)
Bumps [ruby/setup-ruby](https://github.com/ruby/setup-ruby) from 1.310.0 to 1.315.0.
- [Release notes](https://github.com/ruby/setup-ruby/releases)
- [Changelog](https://github.com/ruby/setup-ruby/blob/master/release.rb)
- [Commits](afeafc3d1a...0dafeac902)

---
updated-dependencies:
- dependency-name: ruby/setup-ruby
  dependency-version: 1.315.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-01 20:57:35 +02:00
dependabot[bot]
334796d1c4 chore(deps): bump actions/cache from 5 to 6 (#8961)
Bumps [actions/cache](https://github.com/actions/cache) from 5 to 6.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-01 20:57:09 +02:00
dependabot[bot]
ddf2976e83 chore(deps): bump actions/checkout from 6 to 7 (#8963)
Bumps [actions/checkout](https://github.com/actions/checkout) from 6 to 7.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-01 20:56:45 +02:00
Inverle
6984c22b5c Fix feed dropdown bugs due to hash desync (#8949)
Fixes regression caused by https://github.com/FreshRSS/FreshRSS/pull/8335
To reproduce it, open any dropdown in the aside, reload the page, and try to open the same dropdown - it will disappear

To explain the issue more clearly, the dropdown menu wouldn't show because earlier its opacity is set to 0%, and after clicking the same dropdown after a page reload, the hashchange event wouldn't trigger to restore the previous opacity, and also the second listener would trigger to remove the dropdown if you opened it again.

Maybe https://github.com/FreshRSS/FreshRSS/issues/6156 is relevant here

Also a CSS change to stop showing the dropdown toggle icon of the previously opened dropdown after a page reload while the dropdown was open.
2026-06-28 21:38:37 +02:00
Inverle
dcec27c69d Add SSRF mitigations using filter_var and CURLOPT_RESOLVE (#8400)
* Add SSRF mitigations using `filter_var` and `CURLOPT_RESOLVE`
The idea is to prevent FreshRSS from sending any HTTP requests to internal services, except for the ones that are explicitly allowed in the config.

Based on 6e82b46a48/lib/filelib.php (L3818) and https://github.com/symfony/symfony/blob/8.1/src/Symfony/Component/HttpClient/NoPrivateNetworkHttpClient.php

https://github.com/FreshRSS/simplepie/pull/76
https://github.com/FreshRSS/simplepie/pull/78

* Add allowlist setting in Web UI

* make readme

* Update app/i18n/fr/admin.php

Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>

* make readme again

* make readme

* Further work

Still WIP and needs testing etc.

* Readd previous if check for domain combination allowlist

* Turn POST to GET after redirect

* Improve

* Update config.default.php

Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>

* make readme

* Skip SSRF check if `CURLOPT_PROXY` is set

* make readme

* Fix `!empty()` mistake

* Respect max redirects feed option when fetching with `httpGet()`

* Respect max redirects during SimplePie fetching + fix bypass

bypass fix: `CURLOPT_FOLLOWLOCATION` was moved below so that emulated redirects are enforced.

* Avoid FreshRSS and Minz code in SimplePie
https://github.com/FreshRSS/FreshRSS/pull/8400#discussion_r2935375980

* Corrected hook code

* phpdoc wrong return type

* Add CIDR support in allowlist

* Implement simple DNS caching

* Suppress `dns_get_record()` warnings

* A bit of proof-reading

* Minor typo

* Fix proxy logic

* Fix HTTP POST redirect logic

* Proofread checkCIDR
Add fixes for several situations

* Remove credentials from URL in logs

* Ensure `CURLOPT_FOLLOWLOCATION` is `false` by setting it at the end

* Fix codesniffer long line

* Fix potential bypass due to wrong return value

If there were no records returned by `dns_get_record()`, no overrides to `CURLOPT_RESOLVE` would get passed,
and a potential bypass could occur, when cURL would try to resolve the domain by itself.

* Put the URL at the end in logs

* Add documentation and environment variable support

* make readme

* Fix wrong behavior in case of IP

* Fix duplicate selector in CSS

* Minor type check change

* i18n fr, en

* Minor type check change

* Fix whitespace i18n fr

* make fix-all

* Fix `$ips_ok` not being returned after domain records were cached

* make readme

* PHPStan fix

* make readme

* Minor syntax in SimplePie

* Only return `null` if no allowed IPs were found

* Add wildcard *, help message

* Consistent docs with help message

* i18n: pl

* SimplePie compatibility PHP 7.2

* make fix-all

* Sync SimplePie
* https://github.com/FreshRSS/simplepie/pull/76

* 💥 Breaking change in the Changelog

* Document `INTERNAL_HOST_ALLOWLIST` in Docker docs

* Remove `Cookie` and `Authorization` headers in `httpGet()` during cross-origin redirect

* Minor whitespace
And same comment convention than below

* Remove authentication headers and change POST to GET on redirect in SimplePie

* Remove .local in Docker example

* Fill in default ports when comparing URL origins

* Remove .local from other places than the Docker example

* Rewrite WebSub subscribe to use `httpGet()`

* make fix-all

* Also unset `CURLOPT_USERPWD` during redirects

* phpcs fix

* Always unset `CURLOPT_FOLLOWLOCATION`

* Bump SimplePie
https://github.com/FreshRSS/simplepie/pull/78

* Update logic for CURLOPT_FOLLOWLOCATION

* Fix PHPStan

* Changelog fix security section

* Update most common RSS Bridge case
https://hub.docker.com/r/rssbridge/rss-bridge

* Replace misleading 127.0.0.1:8080 example for Docker
This does not make sense for a Docker container

---------

Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
2026-06-28 18:51:04 +02:00
Alexandre Alapetite
d9dc140a11 Minor bump SimplePie (#8947) 2026-06-27 22:14:25 +02:00
Thomas Vermeer
3161fd1333 fix: move disable button before remove button in user management (#8944)
* fix: move disable button before remove button in user management

The order of action buttons when modifying a user was inconsistent.
The "disable" button appeared after the "remove" button, which is
counterintuitive since disabling a user is a less destructive action
than removing one.

This commit reorders the buttons in `details.phtml` to reflect the
severity of each action, from least to most destructive:
update → purge → promote/demote → disable/enable → delete

Closes #8877

* Remove blank lines

---------

Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
2026-06-27 14:29:48 +02:00
Inverle
931d62ab08 Use timing safe comparison for tokens everywhere (#8945)
I already changed some places that were using normal string comparison to hash_equals before, but didn't check other places.

Now the checks should be consistent.
2026-06-27 14:02:20 +02:00
dependabot[bot]
69eff07d92 chore(deps): bump concurrent-ruby (#8940)
Bumps the bundler group with 1 update in the /docs directory: [concurrent-ruby](https://github.com/ruby-concurrency/concurrent-ruby).


Updates `concurrent-ruby` from 1.3.6 to 1.3.7
- [Release notes](https://github.com/ruby-concurrency/concurrent-ruby/releases)
- [Changelog](https://github.com/ruby-concurrency/concurrent-ruby/blob/master/CHANGELOG.md)
- [Commits](https://github.com/ruby-concurrency/concurrent-ruby/compare/v1.3.6...v1.3.7)

---
updated-dependencies:
- dependency-name: concurrent-ruby
  dependency-version: 1.3.7
  dependency-type: indirect
  dependency-group: bundler
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-23 22:00:22 +02:00
dependabot[bot]
6f1b66eb2b chore(deps-dev): bump faraday (#8939)
Bumps the bundler group with 1 update in the /docs directory: [faraday](https://github.com/lostisland/faraday).


Updates `faraday` from 2.14.2 to 2.14.3
- [Release notes](https://github.com/lostisland/faraday/releases)
- [Changelog](https://github.com/lostisland/faraday/blob/main/CHANGELOG.md)
- [Commits](https://github.com/lostisland/faraday/compare/v2.14.2...v2.14.3)

---
updated-dependencies:
- dependency-name: faraday
  dependency-version: 2.14.3
  dependency-type: indirect
  dependency-group: bundler
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-23 21:45:35 +02:00
Nathan
521981a96a Document PHP-FPM write restrictions for data path (#8936)
Closes #8882
- avoid recommending `/usr/share/` for writable FreshRSS data in the general install docs
- update the Debian/Ubuntu git install path and symlink example to `/var/www/FreshRSS`
- add an FAQ entry for PHP-FPM hardening that can make `/usr/` read-only despite correct Unix permissions
2026-06-22 12:25:06 +02:00
Salvador Castro
80d1db8dc7 docs: mention THIRDPARTY_EXTENSIONS_PATH override in extension install instructions (#8930)
Closes #8927
2026-06-21 19:09:42 +02:00
Antonio Santos
bcf536290d Revert "fix(themes): keep article nav bar pinned to bottom in iOS standalone PWAs (#8933)" (#8934)
This reverts commit 5c5b0b9e66 (PR #8933).

The `transform: translateZ(0)` added to #nav_entries promotes the element to
its own compositor layer, but a non-`none` transform also makes the element a
containing block for `position: fixed` descendants and establishes a new
stacking context. That side effect introduced a regression, so this restores
the previous behaviour while a less invasive fix for #8829 is worked out.

Reverting the whole PR (CSS + CREDITS.md) for a clean rollback.
2026-06-21 18:07:42 +02:00
Antonio Santos
5c5b0b9e66 fix(themes): keep article nav bar pinned to bottom in iOS standalone PWAs (#8933)
* fix(themes): keep article nav bar pinned to bottom in iOS standalone PWAs

On iOS standalone PWAs, WebKit fails to repaint `position: fixed` elements
after a programmatic scroll, so the article navigation bar (#nav_entries)
drifts to the middle of the screen after tapping the prev/next buttons
(which scroll the article container in JS). This does not happen in regular
Safari, where the browser chrome provides repaint hooks the standalone shell
lacks.

Promote #nav_entries to its own compositor layer (transform: translateZ(0))
so WebKit keeps it pinned to the viewport across programmatic scrolls.

This supersedes #8833, whose `top: auto` was a no-op: nothing in the
stylesheet ever sets a `top` value on #nav_entries, so it could not affect
positioning.

Fixes #8829

* Add Antonio Santos to CREDITS.md

* fix(themes): drop redundant will-change on #nav_entries

The compositor layer that keeps #nav_entries pinned in iOS standalone PWAs
comes entirely from transform: translateZ(0). The accompanying
will-change: transform was redundant — it only hints at upcoming changes and
adds nothing to an element already promoted to its own layer.

Removing it leaves the bar pinned across programmatic scrolls (verified on an
iOS standalone PWA), with no behavioural change.

Based on maintainer feedback on #8933.
2026-06-20 21:41:44 +02:00
Jeremy Nguyen
7c4929439d chore: Fix README CLI typos (#8932) 2026-06-20 08:31:46 +02:00
dependabot[bot]
d257166acb chore(deps): bump markdown-it and markdownlint-cli (#8926)
Bumps [markdown-it](https://github.com/markdown-it/markdown-it) to 14.2.0 and updates ancestor dependency [markdownlint-cli](https://github.com/igorshubovych/markdownlint-cli). These dependencies need to be updated together.


Updates `markdown-it` from 14.1.1 to 14.2.0
- [Changelog](https://github.com/markdown-it/markdown-it/blob/master/CHANGELOG.md)
- [Commits](https://github.com/markdown-it/markdown-it/compare/14.1.1...14.2.0)

Updates `markdownlint-cli` from 0.48.0 to 0.49.0
- [Release notes](https://github.com/igorshubovych/markdownlint-cli/releases)
- [Commits](https://github.com/igorshubovych/markdownlint-cli/compare/v0.48.0...v0.49.0)

---
updated-dependencies:
- dependency-name: markdown-it
  dependency-version: 14.2.0
  dependency-type: indirect
- dependency-name: markdownlint-cli
  dependency-version: 0.49.0
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-17 23:13:10 +02:00
Mohammad Lashkari
9db1ea42fe Update credits (#8924)
* update credits.md

* Fix sort

* fix website url

---------

Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
2026-06-15 11:34:30 +02:00
Alexandre Alapetite
c33356c3d7 Changelog 2026-06-14 21:34:02 +02:00
Mohammad Lashkari
53ca983ef2 Complete the Persian translation (#8923)
Translate remaining TODO and DIRTY entries in the Persian locale.
2026-06-14 21:30:56 +02:00
Inverle
1349c7c1a9 SimplePie: Disallow javascript: URI protocol (#8263)
Follow-up of https://github.com/FreshRSS/FreshRSS/pull/7924
https://github.com/FreshRSS/simplepie/pull/80
https://github.com/FreshRSS/simplepie/pull/65

* SimplePie: Disallow `javascript:` URI protocol

* Sync SimplePie

* Update code to work with SimplePie again

* Partial revert previous commit

* Bump SimplePie

---------

Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
2026-06-13 21:15:36 +02:00