Changelog — JPKCom Post Filter
Versionsverlauf des WordPress-Plugins JPKCom Post Filter.
Versionsverlauf von jpkcom-post-filter. Die Einträge stammen unverändert aus der README des Projekts. Eine ausführliche Anleitung mit Tipps findest du im Projekt-Guide.
1.4.3
- Fixed: the 1.4.1 release note said the German translation had fallen behind "for three releases". The actual figure is twelve: the catalogue was generated at 1.1.2, and 1.1.3, 1.1.4, 1.1.5, 1.1.6, 1.1.7, 1.2.0, 1.2.1, 1.2.2, 1.2.3, 1.3.0, 1.3.1 and 1.4.0 all shipped with it stale before 1.4.1 regenerated it. The wrong number understated how long the gap had gone unnoticed, which was the whole point of the note. The 1.4.1 entry has been corrected and marked. No code is affected — this release exists so the corrected note reaches the sites that read the published changelog.
1.4.2
- Added: 46 German translations that were missing. Affected were the three block titles and their descriptions and search keywords, the Elementor and Oxygen widget labels, several settings hints, and the updater's security messages — all of which appeared in English on a German site. The Abilities API texts stay English on purpose: they are read by AI clients and automation, not in the admin area, and their exact wording is what lets a caller correct a mistaken request in one attempt.
- Fixed: the confirmation dialog shown when removing a filter group with its own taxonomy printed the six characters
\u2014instead of a dash. PHP reads that escape only in its braced form, so the plain one stayed in the text and was shown as-is. - Hardened: the translation check added in 1.4.0 reported every entry carrying a context — the block titles, descriptions and keywords, sixteen in total — as missing from the compiled catalogue, although they were present. It now reads the context the way WordPress stores it.
1.4.1
- Fixed: the German translation had not kept up with the plugin for twelve releases. The translation catalogue was last generated at version 1.1.2, so every text added since — including all of the messages the abilities return — appeared in English on a German site, with nothing anywhere to indicate it. The catalogue now covers the whole plugin again. The existing German translations are unchanged; the newly added texts are listed but not yet translated, so they still appear in English until they are. This entry as originally published said three releases. The figure is twelve — 1.1.3 through 1.4.0 all shipped with the stale catalogue; see 1.4.3
- Hardened: the build now fails when the translation catalogue falls behind the code. Regenerating it was never an automated step and was not on the release checklist, which is how it went unnoticed for twelve releases. It is now both.
1.4.0
- Changed: WordPress 7.0 is now the minimum. Up to 6.9 an unexpected error inside an ability callback ended the whole request with a blank page instead of a readable message, and the plugin carried its own guards against that. From 7.0 WordPress catches it itself. The guards stay in place, but the plugin is no longer tested against 6.9 and no longer claims to run there.
- Hardened: a build check now compares the list of input keys each ability accepts against the list it publishes in its schema. The two are written separately, and nothing compared them before. Had they drifted apart, a caller sending exactly what the schema describes would have been told the key does not exist — the plugin refusing a request that was correct. The check fails the build on either kind of mismatch.
- Changed: the "no input given" default of both abilities is now written directly as an empty object instead of being produced by a helper. What clients receive is unchanged, and it stays an object rather than an empty list.
1.3.1
- Fixed: the query ability is declared read-only, but reading a post's excerpt made WordPress' embed handler run — and with no post to attach its cache to, it stored the result as a published post of its own and fetched the linked address from the third-party site while doing so. One request could therefore add rows to the database and contact outside hosts, and any logged-in user was allowed to trigger it. The excerpt is now read with that handler switched off for the duration and switched back on afterwards, so the text you get back is unchanged.
- Fixed: clearing the cache never cleared the fastest layer of it. Saving a post, editing a term, saving the settings, and both "Clear cache" buttons in the admin all reported success while leaving stale query results in place until they expired on their own — up to an hour by default. This affects every site, whether or not the abilities are used.
- Fixed: a search term longer than 1600 bytes was thrown away by WordPress itself, inside the query and without any warning, so the answer was the same one you would have got without searching at all — and nothing in the response said so. Such a term is now refused with a message naming the limit. The limit counts bytes, so accented characters and emoji use more than one each.
- Fixed: the shareable filter link was handed out even when a search term had narrowed the result set. The link cannot carry a search term, so it led to a larger, different list than the one reported beside it — in the clearest case, an answer of "no results" next to a link showing six posts. The link is now withheld in that case; the results themselves are still returned in full.
- Fixed: a filter sent at the wrong nesting level was neither applied nor rejected. The response was a normal, successful-looking answer containing every post on the site, with the filter section empty. Unrecognised input is now refused with a message naming both the key it rejected and the ones it accepts.
- Changed: two statements in the developer documentation were corrected. The claim that nothing unpublished can be reached through the abilities was unconditional and did not hold — the restriction lives in the query the plugin builds, and another plugin can change that query before it runs. The behaviour is unchanged; the description of it now matches.
1.3.0
- Added: the plugin now registers two read-only WordPress Abilities,
jpkcom-post-filter/list-filtersandjpkcom-post-filter/query-posts, in the sharedjpkcom-contentcategory. MCP clients, REST automation and the WordPress AI client can ask which taxonomies and terms a post type can be filtered by, then run a filtered, paginated query and receive both the results and a shareable filter URL. - Added:
JPKCOM_POSTFILTER_ABILITIES(defaulttrue) withdraws both abilities from REST and MCP when set tofalseinwp-config.php, plus the filtersjpkcom_postfilter_ability_metaandjpkcom_postfilter_ability_capabilityfor per-ability control. - Hardened: a filter naming a taxonomy that does not exist previously produced the complete unfiltered result set, because the query builder drops such a clause silently. The query ability now rejects it and names the valid taxonomies, so a caller can correct itself instead of presenting the whole site as a filtered answer.
- Hardened: the query ability always passes a positive page size. The shortcode default of
-1setsno_found_rows, which reports a total of zero regardless of how many posts exist. - Hardened: a single request can filter by at most 50 term slugs per taxonomy; anything beyond that is dropped rather than rejected, so an over-eager caller still gets an answer instead of an error. Without the cap one request could ask for thousands of slugs and turn into a query of the same size.
- Hardened: a search term passed to the query ability is answered from the database rather than from the query cache. Search text is free-form, so caching it would let a caller fill the object cache and APCu with one entry per phrase.
- Changed: the query ability returns an empty
filter_urlfor post types that have no archive page, such aspage. It previously returned a relative address like/filter/news/, which no rewrite rule serves — a link that would have 404'd. - Fixed:
filters,unknown_termsand each post'stermsare sent as{}when they hold nothing, rather than as[]. The output schema declares all three as objects, so a client validating the response against that schema rejected the empty case — which is the most common case forunknown_terms. - Fixed: both abilities can now be called with no input at all. Every parameter is optional, but a bare call was answered with
ability_invalid_input— "input is not of type object" — so the most natural way to ask "which filters does this site have?" was the one way that did not work. - Fixed: the query ability no longer hands out a
filter_urlthat leads somewhere else. The link is now returned only when opening it shows exactly the posts reported next to it, and is left empty in four cases: the post type has no archive page; the site caps the number of terms per taxonomy or the number of taxonomies below what was asked for, so the front end would honour only the first few; a page after the first was requested with a page size that differs from the site's own "posts per page" setting, which is the unit the page number in the URL is counted in; or the requested page lies past the last one, where the URL answers 404. The full result set is returned in every one of those cases — only the link is withheld. - Fixed: the top-level
defaultof both abilities' input schemas is now sent to MCP clients as{}rather than[]. The schema declares it as an object, and WordPress corrected the value on its own REST route but not on the one MCP clients read, so a client that validates schemas saw a contradiction. - Fixed: asking for a page beyond the last one reported
total: 0andtotal_pages: 0next to the page number that was requested — a response that contradicted itself and read as "this site has no posts". It now reports the real totals with an empty post list. - Fixed: caller mistakes — an unknown taxonomy or a post type that is not enabled — are answered over REST with HTTP 400 instead of 500. Both messages name the valid values so the caller can correct itself, which a 500 undoes by telling automation it hit a server fault and should retry the same request unchanged.
- Hardened: a filter group whose taxonomy is no longer registered — the usual cause is deactivating the plugin that provided it — is no longer offered as filterable. Filtering by it used to be accepted on the strength of the configuration alone and then answered with the complete unfiltered corpus; it now returns the unknown-taxonomy error naming the taxonomies that really exist.
1.2.3
- CI: the lint and guard workflow now also runs on pushes to
main. It only covered pull requests, so a direct push with bypass rights skipped every check - Changed: comments, workflow step names and CI output across the repository are now English throughout, and the developer notes in
CLAUDE.mdwere translated and trimmed. No effect on the shipped plugin
1.2.2
- Changed:
Tested up toraised to WordPress 7.1 - Changed: the bundled updater's runtime floor now matches the plugin's own minimum. It bailed out below WordPress 6.8 while the plugin header has required 6.9 for several releases, so the check could never fire on a supported installation
- CI: the release manifest's fallback values for
requiresandtestednow say 6.9 and 7.1. They only apply when the README metadata cannot be read, but a stale fallback would have published a minimum the plugin no longer supports
1.2.1
- Added: plugin banners (
assets/banner-1544x500.avif,assets/banner-772x250.avif) — a plain#3c4955surface with no lettering. The update manifest already advertised these two URLs, but nothing was published under them, so the plugin card in wp-admin had a broken banner
1.2.0
- Added: filter requests are answered with just the swappable zones instead of a complete page. The theme header, nav menus, sidebar widgets and the entire asset pipeline are skipped; the theme's loop still runs, because in auto-inject mode it produces the result markup. Measured on a test install: 60–72 % less transferred, 5–19 % less server time. The request goes through a
/jpkpf-fragment/URL segment rather than a query parameter, so a page cache that strips unknown parameters cannot serve a bare fragment to an ordinary visitor - Fixed: the
noindexfor filter URLs with unknown term slugs never took effect on a Rank Math site. Rank Math discards everywp_robotscallback before writing its own tag, so the protection added in 1.1.7 was inert on exactly the sites it was written for. The rule now also hooks Rank Math and Yoast - Fixed: on a filter URL with no results the "no posts found" message and the whole filter bar were rendered below the footer. They now appear where the listing sits when there are results
- Fixed: using the browser's back button after filtering left the previous results on screen and the filter buttons pressed — the history entry created by the initial page load carries no state, and the handler ignored it
- Fixed: paginating a filtered list quietly stopped using AJAX. WordPress's canonical redirect did not recognise the fragment segment and appended the page it thought was missing, so the request 404'd and fell back to a full reload
- Fixed:
apcu_cache_info()raised a PHP warning wherever APCu is loaded but inactive for the running SAPI —apc.enable_clidefaults to 0, so every WP-CLI call produced one. Withdisplay_errorson it was printed into the response body - Added: rewrite rules are flushed once after a version change.
register_activation_hookdoes not fire on an update, so new rules would otherwise never reach the database - Added:
tests/browser-check.mjsdrives a real filter click in headless Chromium
1.1.7
- Fixed: the settings-directory containment check was a tautology — it compared
realpath( WP_CONTENT_DIR )with itself and never referenced the directory being validated, so it never fired. SinceJPKCOM_POSTFILTER_SETTINGS_DIRis overridable fromwp-config.phpand holds PHP files that are laterincluded, the path is now genuinely verified to resolve insidewp-content - Hardened:
jpkcom_postfilter_build_query_args()no longer copiesmeta_key,meta_value,meta_query,s,author,yearandmonthnumverbatim intoWP_Query. Each is now coerced to a safe shape andmeta_queryis not forwarded at all; callers that need it use thejpkcom_postfilter_query_argsfilter. No current caller passed user input, so this closes a latent path rather than an exploited one - SEO: filter URLs referencing term slugs that do not exist are now marked
noindex, follow. They still return 200 with zero results, so existing links keep working, but they no longer generate unlimited indexable, self-canonicalising thin-content URLs. The check reuses the cached per-taxonomy term list and is keyed by taxonomy, never by the requested slugs - Added:
tests/test-security.php— regression tests for all of the above, each written to fail against the previous implementation. Run in CI on every pull request
1.1.6
- Security: update packages are now verified before installation — the verified file is handed to WordPress instead of being downloaded a second time, so the bytes that were checked are the bytes that get installed
- Security: a missing or unfetchable SHA-256 checksum now aborts the update instead of installing unverified code (previously it silently skipped verification)
- Security: pinned every GitHub Action to a full commit SHA and added Dependabot with a 7-day cooldown, so a moved tag can no longer change the release build
- Security: tightened which download the updater claims, so sibling plugins cannot match each other's package
- Fixed:
sprintf()calls in the updater bound named arguments to a variadic parameter, which raisesArgumentCountErroron PHP 8.3 - Fixed: the "View Details" modal could fail with a
TypeErrorwhen the manifest omittedrequires_plugins - Performance: a failed manifest fetch is now cached for an hour instead of being retried on every admin request
- Added: CI workflow on every pull request (PHP lint, named-argument check, YAML validation, action-pinning guard)
- Housekeeping: removed stray editor backups and an unused
messages.mofrom the release package
1.1.5
- Raised "Tested up to" to WordPress 7.0
- Normalized license fallback defaults (updater and release workflow) to
GPL-2.0-or-laterwith the HTTPS license URI
1.1.4
- Security: updater prefers an exact match against the manifest
download_urlover the slug heuristic, so a tampered manifest can no longer bypass the checksum gate - Security: timing-safe checksum comparison (
hash_equals()) with anis_string()guard againsthash_file()failures - Security: manifest fetch via
wp_safe_remote_get()(SSRF defense-in-depth) - Fixed PHP warning and missing contributor names in the plugin detail popup (
display_namenow provided) - Fixed PHP warning/deprecation on
wp plugin listby completing theno_updatetransient entry (new_version,package,tested,requires_php)
1.1.3
- Plus/Minus Icon styling — New CSS variables (
--jpkpf-pm-color,--jpkpf-pm-font-size,--jpkpf-pm-font-weight) for independent styling of the +/– icons in filter buttons - Layout & Design → Filter tab — New "Plus/Minus Icon" section with Color, Font Size, and Font Weight fields
- Force no underline — New checkbox in Layout & Design → Advanced that applies
text-decoration: none !importantto all filter buttons and icons, fixing themes that force underlines on links - Translations — Updated de_DE and de_DE_formal translations for all new strings
1.1.2
- Plugin Updater — Fixed manual ZIP upload failing with "invalid URL" error by adding
wp_http_validate_url()check inverify_download_checksum() - Release Workflow — Fixed ZIP packaging without top-level directory; WordPress now correctly recognises the update by using a staging directory with the plugin slug
1.1.1
- .github/workflows/release.yml — "Build Gutenberg blocks" npm ci bugfix
1.1.0
- Gutenberg Blocks — Three native blocks (Post Filter, Post List, Post Pagination) with live server-side preview, InspectorControls, and Full Site Editor support
- Elementor Widgets — Three widgets (Post Filter, Post List, Post Pagination) in a dedicated category, loaded only when Elementor is active
- Oxygen Builder Elements — Three elements (Post Filter, Post List, Post Pagination) using the OxyEl API, loaded only when Oxygen Builder Classic is active
- Block pre-scan — Pagination blocks can be placed above or below the list block; the plugin pre-scans the block tree (FSE templates and post content) before rendering to ensure correct query availability
- AJAX pagination swap — Standalone pagination elements (blocks/shortcodes/Elementor) are updated during AJAX filter requests via
data-jpkpf-paginationattribute, keeping pagination links filter-aware - Pagination placeholder — When filter selection reduces results to a single page, pagination is hidden but preserved as a DOM placeholder; it automatically reappears when filters change back to multiple pages
- Auto-inject guard — Prevents duplicate pagination insertion in auto-inject mode during AJAX swaps
1.0.0
- Initial release
- Faceted filtering for any post type and taxonomy
- Four filter layouts: bar, columns, sidebar, dropdown
- Three list layouts: cards, rows, minimal
- SEO-friendly URL schema with WordPress rewrite rules
- AJAX filtering with
history.pushState - Auto-inject mode for archive/blog pages with configurable pagination position
- Shortcodes:
[jpkcom_postfilter_filter],[jpkcom_postfilter_list],[jpkcom_postfilter_pagination] - Interactive shortcode builder in admin
- Custom taxonomy registration from Filter Groups admin page
- Plus/Minus interaction mode for filter buttons
- Show More button with configurable threshold
- Four predefined color schemes (Default, Dark, Contrast, Monochrome)
- Three stylesheet modes (Full, Variables only, Disabled)
- Reset button visibility modes (Always, On selection, Never)
- Bare Endpoint Behaviour (404, redirect to home, custom URL)
- Max. Filters per Group limit (URL + JS enforcement)
- Four-layer caching (object cache, transients, APCu, file cache)
- Settings import/export (JSON)
- Translations: de_DE, de_DE_formal