### Motivation
- The GReader API enabled `JSON_THROW_ON_ERROR` globally and streams items with `json_encode()` without catching `JsonException`, so malformed UTF-8 in feed-derived fields could trigger an uncaught exception and abort streaming responses (resulting in 500/partial responses).
### Description
- Add `JSON_INVALID_UTF8_SUBSTITUTE` to `JSON_OPTIONS` in `p/api/greader.php` so `json_encode()` substitutes invalid UTF-8 bytes instead of failing while keeping `JSON_THROW_ON_ERROR` and other flags unchanged.
### Testing
- Ran a syntax check with `php -l p/api/greader.php`, which completed successfully.
------
[Codex Task](https://chatgpt.com/codex/cloud/tasks/task_e_69b8831ec0c48324810ea9ec05c16493)
* Reorder add feed and add category
Implements some suggestions made in https://github.com/FreshRSS/FreshRSS/issues/2014
Changes proposed in this pull request:
- Move "Add feed" as the first section of the Subscriptions add page.
- Add checkbox "Keep adding feeds" to stay on the "add" page.
How to test the feature manually:
1. Navigate to the page "Add a feed or category"
2. See new order.
3. Type URL of a new feed, select a category, and check "Keep adding feeds "
4. See FreshRSS confirm the feed was added while staying in the same page.
* Add checkbox to stay in the add feed page
* Update i18n
* PHP CS
* Manually set i18n progress
* Change i18n
---------
Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
* Refactor FeedsListBeforeActualize to accept Traversable as a response.
* Fix returning the first feed
* Fix object not being updated with all changes to the DB.
* Update app/Controllers/feedController.php
Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
---------
Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
Closes https://github.com/FreshRSS/FreshRSS/issues/8508
Changes proposed in this pull request:
- Use an integer for `Feed::error` everywhere (follow up to #8646)
- Extract `Entry::machineReadableDate()` into function for use in HTML templates
- Add `timeago()` function that converts a unix timestamp into a "4 weeks ago" string
- Show the last successful feed update, and the last erroneous update
How to test the feature manually:
1. Update a feed
2. Modify the feed URL in the database and set it to a non-existing URL
3. Update the feed again
4. Open the "Manage feed" and see the expanded error message:
> Blast! This feed has encountered a problem. If this situation persists, please verify that it is still reachable.
> Last successful update 3 hours ago, last erroneous update 1 hour ago.
You can hover the relative dates to see the timestamp.
* Make Feed::error an int everywhere
Related: https://github.com/FreshRSS/FreshRSS/pull/8646
* Extract timestamptomachinedate()
.. for later usage in the feed error time display.
* Show time since when a feed has problems
We add our own "timeago" function that converts a unix timestamp
into a "4 weeks ago" string.
Resolves: https://github.com/FreshRSS/FreshRSS/issues/8508
* Add new translation keys
* i18n fr, en-US
* Minor XHTML preference
* Slightly shorter rewrite, also hopefully easier to read
* Rewrite to allow (simple) plural
I also moved some functions around for hopefully a more generic and better structure.
I made some changes for the sake of speed (e.g. second-based logic instead of datetime intervals).
Note: I used automatic translation as I was worried it would be too complicated to explain to translators... I proofread the few languages I have some familiarity with.
* Add reference to CLDR
* Slightly more compact syntax
* Always show last update, fix case of unknown error date
* Remove forgotten span
* No need for multi-lines anymore
* Fix error date thresshold
* plurals forms
* Extract gettext formula conversion script to cli
* Simplify a bit
* Escort excess parentheses to the door
* Simplify
* Avoid being too clever in localization
* Fix German
* Fix plural TODO parsing
* Ignore en-US translation
* make fix-all
* git update-index --chmod=+x cli/compile.plurals.php
* Heredoc indent PHP 7.3+
* compileAll: Continue on error
* PHP strict comparisons
* Light logical simplification
* Cache plural_message_families
* Avoid case of empty value
* A bit of documentation
---------
Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
Co-authored-by: Frans de Jonge <frans@clevercast.com>
Co-authored-by: Frans de Jonge <fransdejonge@gmail.com>
* Add configuration to mark same GUID as read in category.
Implement suggestion mentioned in #8641
Changes proposed in this pull request:
- Add a configuration to the categories to automatically mark entries as read if the same GUID is present in recent entries.
How to test the feature manually:
1. Create a category. Check the option "Mark an article as read… if an identical GUID already exists [...]"
2. Enable debug logs, or add an extension which registers to the hook `Minz_HookType::EntryAutoRead`.
3. Add feeds which might have the same GUID.
For example: https://www.reddit.com/r/technology/hot.rss, https://www.reddit.com/r/technology/rising.rss, https://www.reddit.com/r/technology/best.rss, and https://www.reddit.com/r/technology/new.rss
4. See the logs "Mark GUID as read[...]", or the effect of the extension.
* Implement behavior to mark same GUID as read in category.
* Update documentation
* Update i18n
* Fix PHP CS report
* Fix missing argument
* Fixes
---------
Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
* Add form to create user queries
Closes https://github.com/FreshRSS/FreshRSS/issues/6361
Changes proposed in this pull request:
- Add a form to create new user queries on the User Queries page
- Fix the controller to properly handle request data
How to test the feature manually:
1. Open FreshRSS
2. Open Settings
3. Click User queries
4. Create new user query
* Add tranlation key for title in create new user query
* Fix 'for' conflict with aside in labels
* Fix input widths
* i18n: fr
* make fix-all
* Fix conditions in configureController
* Remove token condition
* Fix ctype_digits condition
* Fix errors
* Fix phpStan error
* Fix syntax and state for checkboxes
* Add new way to create user queries in docs
* Compress image more
---------
Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
* Add FeedsListBeforeActualize hook.
Closes#8650
Implement new hook to allow extensions to modify the list of feeds to actualize.
How to test the feature manually:
1. Add several feeds from a single site (e.g. reddit and YT).
2. Add feeds to other sites.
3. Add and enable the extension https://github.com/pe1uca/xExtension-Declumping
4. Call `php app/actualize_script.php` to update feeds with a different order.
A log like this one is needed to properly see the behavior.
```php
foreach ($feeds as $key => $value) {
syslog(LOG_INFO, "$key: {$value->name()} ({$value->url()})");
}
```
Sort in alphabetical order.
* Implement call to hook
* Remove duplicate return
* Fix PHPStan error
* Update documentation
* Sanitize hook response
* Markdown cleaning
---------
Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
* prefer feed.icon
Closes#5518
Changes proposed in this pull request:
- When a feed provides an icon URL (<image><url> in RSS 2.0/1.0, <atom:icon>/<atom:logo> in Atom, icon/favicon fields in JSON Feed), that URL is stored as a feedIconUrl attribute on the feed and used as the primary source for favicon downloads, instead of scraping the feed's website for <link rel="icon"> tags.
- If the feed-provided icon URL fails to return a valid image, the existing fallback chain (website HTML favicon search → /favicon.ico) is preserved.
Custom favicons uploaded by users always take priority and are never overridden.
How to test the feature manually:
1. Add an RSS feed that includes a <image><url> element (e.g. an RSSHub feed: `https://rsshub.app/youtube/channel/UC2cRwTuSWxxEtrRnT4lrlQA`). After actualization, confirm the feed's favicon matches the avatar image from the feed, not the Bilibili site favicon.
2. Add an Atom feed containing <atom:icon> or <atom:logo> Confirm the feed icon is used.
3. Add a JSON Feed (spec: icon field). Confirm icon is preferred over favicon when both are present.
4. Temporarily point a feed's <image><url> to a broken URL. Confirm FreshRSS falls back to the website favicon silently.
5. Upload a custom favicon for a feed, then actualize it. Confirm the custom favicon is not replaced.
<img width="470" height="317" alt="image" src="https://github.com/user-attachments/assets/17445154-d94c-44d6-b7e7-019bf24c5767" />
* fix(favicon): use htmlspecialchars_decode for feed image URL
* Decode quotes as well
* New function in our SimplePie fork
https://github.com/FreshRSS/simplepie/pull/73
---------
Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
* Convert feed view into grid where appropriate
This makes the feed view prettier on mobile, if thumbnails and summary are shown, as discussed in https://github.com/FreshRSS/FreshRSS/discussions/8629
**Changes proposed in this pull request:**
- Converts Feed Item list into multiple lines
- But only if both thumbnails and summaries are shown
The code is quite different from what I had done in my own hack: There I had used `display: flex` and then counted items, but that only works for my own hack: here we don't know how many items a given user may have, so we use `display:grid` instead with name grid areas.
**How to test the feature manually:**
1. Ensure you enable both thumbnails and summaries
2. Check out the feed view on mobile
3. Play around with enabling and disabling bookmarks, share button, etc.
**Pull request checklist:**
- [x] clear commit messages
- [x] code manually tested
- [ ] unit tests written (optional if too hard)
- [ ] documentation updated
**Screenshots:**
<img width="458" height="1173" alt="SCR-20260324-jnte" src="https://github.com/user-attachments/assets/1d5c1615-961a-4953-8157-f9f8a5470545" />
<img width="459" height="1172" alt="SCR-20260324-jnwf" src="https://github.com/user-attachments/assets/96ffcea3-384f-4de9-9c50-3366022713d8" />
* remove gap
* fix lint errors
* fix style errors
* remove whitespaces
* Reduce use of slow `:has()` CSS selector
---------
Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
* do not show dot, if no unread number is shown
* Update app/Controllers/indexController.php
Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
---------
Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
When the search query includes some feed IDs or category IDs, adjust feed visibility/priority filter to include at minimum feed or category visibility.
Fix: https://github.com/FreshRSS/FreshRSS/issues/8602