=== SRA Nieuwsbank ===
Contributors: infonics
Tags: nieuwsbank, api, nieuws, import, sra
Requires at least: 6.0
Tested up to: 7.0
Requires PHP: 8.0
Stable tag: 1.3.1
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Import articles from the SRA News API into WordPress and display them with filtering and layout options.

== Description ==

SRA Nieuwsbank allows you to import and display articles from the SRA News API directly within WordPress.

Key features:
* Import articles from an external API
* Filter content by themes, target audiences, branches, and types, combined with OR: an article is imported as soon as it matches any selected value
* Manual and scheduled synchronization
* Customizable layout settings
* Ability to edit imported articles within WordPress
* Option to preserve or convert srani posts when editing them
* Choose whether article images are stored in your media library or loaded directly from SRA
* Move images that are already in the library either way, with a dry-run before anything is deleted

This plugin is designed for organizations that want to integrate SRA News content into their WordPress website.

== Installation ==

1. Upload the plugin to the `/wp-content/plugins/` directory.
2. Activate the plugin through the 'Plugins' menu in WordPress.
3. Go to the SRA Nieuwsbank settings page.
4. Configure your API settings.
5. Save the settings and run a manual import if needed.

== Frequently Asked Questions ==

= Do I need API access? =

Yes, you need access to the SRA API to use this plugin.

= Can I manually trigger an import? =

Yes, the plugin provides an option to manually start synchronization.

= Why does a scheduled import not fetch everything in one run? =

Because a large selection or a long history takes more time than the server allows a single request. A scheduled import therefore stops on its own before that limit, records which filter group and page it had reached, and the next scheduled run continues from that point. Once the last filter group has been done, the round is complete and the following run starts at the beginning again. New articles do not have to wait for that: every scheduled run first fetches the newest page of filter groups, and only then continues working through the backlog. That sweep has its own position and its own share of the time budget, so it carries on from where it stopped and rotates through all filter groups over successive runs, while the backlog keeps moving as well. A run that stops on its time budget or that could not fetch a filter group says so in the error log. Changing the filter selection or the import mode discards the recorded position and starts a fresh round. The start year does the same only when it moves the date a round starts from: a scheduled import never reaches back past its boundary date (see the next question), so a start year before that date leaves its range, and its position, untouched.

= Why does a scheduled import only bring in recent articles? =

Because fetching the whole history is a deliberate act, not something an update should set off on every site at once. The first time the scheduled import runs it records that day as its boundary date, and from then on it only takes articles published on or after it. Articles older than that boundary are left alone entirely: they are neither newly imported nor updated when they change at the source. Everything older is handled by the "Handmatige import" button, which ignores that boundary and covers the full range from the configured start year, updating already imported articles along the way when the import mode allows it. So after changing the filter selection — or after updating to a version that matches more articles than before — press that button once to collect what the scheduled import will not fetch on its own. The admin page shows the boundary date next to that button.

= Can I customize the layout? =

Yes, you can adjust layout settings.

= Can I stop the imported images from filling up my media library? =

Yes. Set "Afbeeldingen" on the API tab to "Extern laden vanaf SRA". From then on new imports reference the image at sra.nl instead of storing it, and nothing new appears in your media library. Images that are already in your library are left alone and keep working.

= My media library is already full of imported images. Can I clear it out? =

Yes, with the action "SRA-afbeeldingen verwijderen" under Acties > Afbeeldingen. It first shows you what it would do — how many images, how much disk space, which articles are involved — and removes nothing until you confirm. Articles that use such an image as their featured image are repointed to the same image at sra.nl first, so they keep showing it. Only media this plugin imported is touched; the rest of your library is not.

An image that is in use and that sra.nl no longer serves is left alone, so an article never ends up with a broken image. Where the plugin finds a possible reference elsewhere — in article text, a custom field or a page builder — it says so and leaves the image alone unless you decide otherwise; that check is a hint, not a guarantee, because such a reference is only recognisable by the attachment ID or the file name. The action "SRA-afbeeldingen importeren" downloads everything back if you change your mind.

Be aware of five limitations before switching: only one image size is available instead of several (WordPress cannot generate thumbnails from an image it does not have), themes cannot crop the image, if sra.nl is unavailable the image is broken on your site because there is no local copy, the references are resolved by this plugin so they render broken while it is deactivated, and your visitors connect to sra.nl directly (see the External services section).

== External services ==

This plugin connects to the SRA Nieuwsbank API (https://www.sra.nl) to retrieve news articles and filter options.

= SRA Nieuwsbank API =

**What the service is and what it is used for:**
The SRA Nieuwsbank API is a news feed provided by SRA (Samenwerkende Registeraccountants en Accountants-Administratieconsulenten). This plugin uses it to import and display SRA news articles on your WordPress site.

**What data is sent and when:**
* Your API key (provided in the plugin settings) is sent with every request to authenticate.
* Filter preferences (themes, target audiences, branches, types) and pagination parameters are sent when fetching articles or filter options.
* Requests are made when: saving plugin settings (to verify the API key), during scheduled automatic synchronization (three times daily at 10:00, 13:00 and 17:00 server time), and when a manual import is triggered by an administrator.

**These requests are made by your server, not by your visitors. No personal data of website visitors is sent to the SRA API.**

= Images hosted by SRA (optional, off by default) =

The plugin setting "Afbeeldingen" (SRA Nieuwsbank > API tab) controls how article images are handled:

* **Downloaden naar mediabibliotheek** - the default. Images are downloaded once by your server and stored in your WordPress media library. Visitors load them from your own domain and never contact SRA.
* **Extern laden vanaf SRA** - images are not stored. Article images are referenced at https://www.sra.nl, using the image URL exactly as the SRA API supplies it. These references are resolved by this plugin, so they only work while it is active.

**When "Extern laden vanaf SRA" is enabled, personal data of your website visitors IS sent to SRA.** Every visitor's browser requests the image straight from www.sra.nl, which sends that visitor's IP address, user agent and referring page to SRA. This happens on every page view that shows an SRA article image.

This option is disabled by default and must be switched on deliberately by an administrator. If you enable it, mention this third-party connection in your own privacy policy; under the GDPR this is the same category as loading fonts or scripts from a third party. The setting states this on screen before you use it.

Switching back to "Downloaden naar mediabibliotheek" ends the visitor-to-SRA connection for every article that is imported again afterwards.

= Moving images that are already in the library =

Both settings only govern new imports. The two actions under Acties > Afbeeldingen move media that is already there:

* **SRA-afbeeldingen verwijderen** - repoints the articles that use such an image as their featured image to https://www.sra.nl and then deletes the local copy. From that moment those images are loaded by your visitors' browsers directly from SRA, with the same consequences as the "Extern laden vanaf SRA" setting described above. Before deleting anything, your server asks sra.nl (one HEAD request per image in use, a GET only when the server does not answer HEAD) whether the original is still there; an image that is in use and no longer served is left alone.
* **SRA-afbeeldingen importeren** - the way back. Your server downloads each image that an article uses as its featured image once and stores it in the media library; visitors stop contacting SRA for it. A reference that no article uses is not downloaded — that would only add a file nobody sees — and is cleaned up instead.

Both actions are started by an administrator, show what they will do before they do it, and touch only media this plugin imported.

Service provider: SRA (https://www.sra.nl)
Terms of service: https://www.sra.nl/disclaimer
Privacy policy: https://www.sra.nl/privacyverklaring

== Screenshots ==

1. Plugin settings page
2. Filter configuration
3. Frontend article display


== Changelog ==

= 1.3.1 =
* Fix: Selected themes, target audiences, branches and types are now combined with OR instead of AND. An article is imported as soon as it matches any selected value, so picking themes and target audiences together no longer narrows the result to articles carrying both. Each group is requested separately and the results are merged, de-duplicated on article id.
* Fix: A large selection no longer silently imports nothing. The SRA API answers with HTTP 200 and an empty list once the filters in the URL exceed roughly 4096 characters — 94 selected themes is already enough — so every group is now split into blocks that stay well below that.
* Improvement: A manual import no longer stops at the first API error. The filter group that could not be fetched is skipped and the remaining groups are still imported, so one failing group no longer costs the whole run.
* Safety: An import that did not fetch everything says so instead of reporting success. The closing panel reads "Klaar, met fouten" and lists per filter group what went wrong; a scheduled import records the same in the error log.
* Safety: An answer the import cannot trust counts as a failure of that filter group, not as "no articles": an HTTP error code, a body that is not readable JSON, and a page that comes back empty while the API itself says more pages follow.
* Improvement: A manual import that is broken off halfway now keeps what it already established — how many articles were imported and skipped up to that point, and which filter groups failed — instead of showing only a link back to the settings.
* Fix: A scheduled import no longer restarts at the first filter group every time. It now stops on its own before the server's time limit, records where it stopped, and the next scheduled run continues from that point; only a round that reached the end starts over from the beginning. Previously a run that ran out of time lost its position, so the first pages were repeated on every round and the last filter group was never reached on a large selection. Changing the filter selection or the import mode discards the stored position and starts a fresh round, as does the start year when it moves the date the round starts from.
* Improvement: The outcome of a scheduled import is written to the error log, stating separately whether it paused on its time budget (normal, the rest follows next round) or could not fetch a filter group (incomplete). The time budget is 60% of the server's max_execution_time — at least 20 seconds, but never more than 80% of that limit, so a round always cuts itself off before the server does — 120 seconds when there is no limit, and can be adjusted with the `srani_import_time_budget` filter. It is counted from the start of the request rather than the start of the round, because WP-Cron runs every due task in a single request. The position is written down before each page is fetched, so a round that is cut off by the server anyway still resumes at that page instead of at the beginning.
* Improvement: Every scheduled import first fetches the newest page of filter groups before it continues with the backlog. Without that, freshly published articles would have to wait until the round had worked all the way around, which on a long history can take days. That sweep keeps its own position and carries on from there the next run, so with more filter groups than fit in one run it rotates through all of them instead of always covering the same first few. It gets at most half of the time budget and the backlog gets the rest, and each of the two always does at least one page, so neither can starve the other. A page the sweep has just fetched is not fetched again by the backlog: that filter group continues at its second page, or is done straight away when it had only the one.
* Change: A scheduled import no longer reaches back to the configured start year. It records a boundary date the first time it runs and from then on only takes articles published on or after it, so an update that matches more articles than before does not pull a site's whole history in by itself. Articles older than that boundary are also no longer refreshed by the scheduled import. Fetching and updating everything older is done with the "Handmatige import" button, which still covers the full range from the configured start year; the settings page shows the boundary date next to that button.

= 1.3.0 =
* Feature: New setting "Afbeeldingen" with two modes. "Downloaden naar mediabibliotheek" is unchanged and remains the default, so existing sites see no change on update; "Extern laden vanaf SRA" references the image URL supplied by the API and stores nothing locally.
* Improvement: Removed a duplicated copy of the entire import loop from the manual-import AJAX batch; both import paths now use `SRANI_Importer::process_item()`, so the new setting applies to scheduled and manual imports alike.
* Security: Removed the registered `srani_delete_all_images` action. It deleted every image on the site, not just the ones this plugin imported, and had no button.
* Documentation: The External services section now states that visitors' browsers contact sra.nl directly when the external image mode is enabled.
* Feature: Two actions under Acties → Afbeeldingen move media that is already in the library between "stored locally" and "loaded from sra.nl". "SRA-afbeeldingen verwijderen" repoints the articles that use an image as their featured image to sra.nl and then removes the local copy; "SRA-afbeeldingen importeren" is the way back. Both are scoped strictly to media this plugin imported, recognised by `_srani_source_key` or a `_source_url` on an SRA host.
* Feature: The delete action always shows a dry-run first — counts, disk space, examples and an explicit confirmation. Nothing is removed without it.
* Feature: The dry-run separates what it can prove from what it cannot. Use as a featured image is exact and is acted on; a match in post content, a custom field or a page builder is shown as "mogelijk in gebruik" together with the field or post it was found in. Those images are not deleted unless the administrator ticks the extra box. The screen states which references the check does not cover.
* Safety: An image that an article still uses is only deleted when sra.nl actually serves the original at that moment, checked per image during the dry-run.
* Safety: The dry-run records its progress as it goes, so a run that is cut short by a server timeout resumes where it stopped instead of starting over on the same images. A single image that keeps exhausting the server's time limit is tried once, then skipped and reported as not examined, so the examination always finishes.
* Safety: Both actions track their progress on the server, so continuing after a timeout picks up at the next image instead of repeating the batch. An image the run itself stalled on is attempted once, then left alone and listed separately as interrupted, because what did or did not happen halfway cannot be established. The closing panel accounts for every image: processed, skipped, failed and interrupted together add up to what the preview predicted.
* Safety: A step stops starting new images once it has been working for a while, so a large library is handled in more and shorter steps instead of running into the server's time limit; the remaining images move to the next step.
* Safety: A run that was broken off no longer only reports that the preview expired. It says how many of the images were handled, how many of those were actually processed, and that the rest was left untouched — up to a day afterwards, also when the preview itself is long gone.
* Feature: The import preview states its two outcomes separately — how many references are fetched into the media library, and how many are cleaned up because no article uses them — and the confirmation says the same, so the numbers you agree to are the numbers the run produces.
* Safety: Cleaning up an unused reference is a deletion, so it is held to the same standard as the delete action: a reference with any hint of use elsewhere is left alone and shown as "mogelijk in gebruik" with the place it was found.
* Safety: The availability check now also covers an image that only became a featured image after the preview was made — its local copy is kept, not removed, when sra.nl does not serve the original at that moment.
* Feature: The delete preview now separates the two kinds of SRA media it finds, and says so in its counts, its examples and its confirmation: images that are still an article's featured image — those get repointed to sra.nl — and orphans, whose article no longer exists so nothing links to them. Which is which is read from the articles' own featured-image records, not inferred from the image URL.
* Improvement: The dry-run no longer asks the database the same questions once per image. Who uses an image as its featured image is now read in a single pass, and the search for other references is narrowed once per round to the posts that can contain one at all. On a library of 2,876 SRA images this took the preview from just over two minutes to five seconds, with the same outcome down to the individual image; what counts as a reference is unchanged.
* Improvement: When the setting is "Extern laden vanaf SRA" while SRA images are still stored locally, the setting itself now says so, with the count and a link to the conversion preview. The behaviour is deliberately unchanged: switching the setting still converts nothing on its own.
* Improvement: A run that was cut short now reports its numbers in the same words on both paths, and the closing panel lists which images failed or were interrupted instead of showing an empty list under that heading.

= 1.2.8 =
* Improvement: Added support for base64 image sources in article content so embedded images render correctly.
* Feature: Added the option to use Nieuwsbank as primary news source.

= 1.2.7 =
* Feature: Added a URL-triggered admin button to remove legacy `sra_post` articles.

= 1.2.6 =
* Fix: HTML markup fixes in template (closed missing paragraph tags)

= 1.2.5 =
* Maintenance: Added public REST version route and updated plugin metadata.

= 1.2.4 =
* Maintenance: Minor plugin updates and improvements.

= 1.2.3 =
* Feature: Added layout setting to show the imported summary above the content on single SRA articles

= 1.2.2 =
* Improvement: Removed excerpt display from single post content - excerpts no longer prepended to article content

= 1.2.1 =
* Improvement: "Excerpts bijwerken" button is now hidden by default for cleaner UI
* Feature: Button can be shown using URL parameter ?show_excerpt_update=1
* Improvement: Functionality remains fully accessible via direct URL when needed

= 1.2.0 =
* Feature: Summary/excerpt import - Articles now automatically get an excerpt from the API
* Feature: Summary display - Excerpts are shown above content on single article pages
* Feature: "Excerpts bijwerken" button - Update excerpts for existing articles
* Feature: Batch processing for import - Prevents timeouts with progress bar and real-time updates
* Feature: Batch processing for excerpt updates - Prevents timeouts with visual feedback
* Improvement: Fixed timeout issues during large imports
* Improvement: Better user interface with progress indicators
* Fix: Articles no longer automatically convert to regular posts during excerpt updates

= 1.1.11 =
* Feature: Added pagination parameter to shortcode - allows disabling pagination (e.g., [sra_nieuwsbank limit="3" pagination="false"])

= 1.1.10 =
* Code quality improvements: Fixed all WordPress coding standards violations
* Security enhancement: Added escaping to wp_customize_url() output
* Performance optimization: Refactored slow meta_key queries using WordPress API instead of direct database queries
* Code standards: Prefixed all template variables with srani_ prefix to comply with WordPress naming conventions
* Code documentation: Added phpcs:ignore comments with explanations for necessary database queries and security exemptions
* Improved overall code compliance with WPCS standards

= 1.1.9 =
* Enhanced security: Fixed nonce mismatch in delete actions and added API key sanitization
* Improved performance: Added transient caching for API filter responses
* Code quality: Consolidated duplicate canonical URL logic and removed unused code
* Localization: Translated all hardcoded strings in post types, taxonomies, and menus
* Removed CSS customizer
* Updated version and changelog

= 1.1.8 =
* Removed LinkedIn integration (no longer used)
* Fixed: inline CSS now uses wp_add_inline_style() instead of raw echo
* Added nonce verification and capability checks to all admin action handlers
* Renamed option key from api_importer_options to srani_options (with automatic migration)
* Renamed API_Post_Importer_Sync class to SRANI_Sync for proper prefixing
* Changed admin menu position from 20 to 80 (below core WordPress items)
* Removed dead/duplicate settings registration code
* Updated readme.txt with External Services documentation

= 1.1.7 =
* Bug fixes and performance improvements

== Upgrade Notice ==
