=== Klassifieds for WooCommerce — older releases ===

The latest releases are listed in readme.txt.

== Changelog ==

= 0.9.180 =

* The General tab says its two longest settings in plain words. "Images" is now "Product images", with the box "Show images from kleinanzeigen.de instead of copying them" and a short note on what a visitor's browser sends kleinanzeigen.de when it is ticked. The trigger service is now the "Auxiliary service", with the box "Working in a timely manner"; its description says what it does and that it is run by the plugin's author and receives your site address, the plugin version and the edition once a day.
* The dashboard's Published and Deactivated cards call the products that come from an ad "kleinanzeigen.de" instead of "Klassifieds".
* Fixed: after importing several images, German admins saw a literal "{count}" in the message. The German plural named a placeholder the message never filled.
* The readme describes the housekeeping — with "Cleanup regularly" on, a product whose ad has been gone for more than 30 days is deleted — and what uninstalling removes and keeps, `KLASSIFIEDS_REMOVE_ALL_DATA` included.
* For developers: the image-import message's placeholders are `{count}` and `{total}` instead of `{count1}` and `{count2}`. Klassifieds Pro needs no update.

= 0.9.179 =

* A product whose ad comes back is published again, as it was before the ad went away. On very busy accounts it could stay a draft instead: if the plugin found its ad missing a second time before it came back, that second time overwrote the status the product was to return to. Products this already happened to stay drafts; publish them by hand.
* Background jobs no longer run twice at the same time. A job now takes its lock in one step and shares it with every schedule that runs the same job, and a job that finds the lock taken leaves all of that job's work alone.
* For developers: the job lock is a row `klassifieds_job_lock_{task}` in the options table instead of the `klassifieds_job_active_*` transients. `Klassifieds_Account_Installer::delete_active_job_options()` and `delete_active_job_option()` are gone. Klassifieds Pro needs no update.

= 0.9.178 =

* Switching on "Keep background work running on time" registers your site with the trigger service right away, instead of at the next daily run. Switching it off still withdraws the registration right away, and a site that never switched it on sends nothing.
* For developers: every settings page registers its hooks in a public `hooks()`, which `Klassifieds_Settings_Page` calls on `klassifieds_register_settings`. `Klassifieds_Connector::ttl()` returns the trigger service's response body. Klassifieds Pro 0.9.160 and older do not work with this version; update Pro to 0.9.161 first.

= 0.9.177 =

* The plugin reads your whole account. Until now it read only the first page of your ads, about 25, so a larger account never had more of them turned into products. It now goes through the account 100 ads per page, up to the 10,000 kleinanzeigen.de lists for one account. The rest are picked up from your next sync on.
* A read whose number of ads does not match your account's own count, because an ad came or went while it paged or a page failed, no longer replaces the stored list of ads. It only adds the ads that list lacks, so no product is taken out of your shop on its word.
* For developers: `klassifieds_index_limit` defaults to `KLASSIFIEDS_MAX_CAPACITY` (10,000) instead of `null`, and a filter answering `null` now sets no limit at all instead of one page. `klassifieds_page_crawl_size` defaults to 100. `Klassifieds_Data_Cache::is_complete()` is removed.
* For developers: the filters `klassifieds_task_items` and `klassifieds_pre_create_ad_product` are removed, and with them `Klassifieds\Enums\Exception::DEFERRED`. Nothing limits which ads become products any more.

= 0.9.176 =

* The plugin runs no shell commands any more. The server load it samples comes from PHP's own `sys_getloadavg()`.
* Debug messages from the class loader and about a missing template are written only when `WP_DEBUG_LOG` is on.
* The helper that decodes base64-encoded JSON sanitizes every value it returns.
* The update from 0.9.171 or older renames old shortcodes with one prepared statement per tag.
* Removed an unused function that could make a file world-writable.

= 0.9.175 =

* Security: the product list's "save" request accepts only a product's title, slug, excerpt, content, status and menu order, each sanitized, and a status only if WordPress knows it. It used to pass any post field from the request on to WordPress, the post ID included, so a user allowed to manage the plugin could have changed a different post. It now also checks that the user may edit that product, and may delete it before a trashed product is deleted.

= 0.9.174 =

* The plugin's data under its old names is removed on the first request after the update: the tables `ka_meta_lookup`, `ka_quarantine` and `ka_deleted_posts`, the `_ka_*` meta of products and images, and the old colour of labels. 0.9.173 copied all of it to the new names. A site updating from 0.9.171 or older copies and removes it in one go, and removes nothing if the copy fails.
* After this update, do not go back to 0.9.171 or older: those versions would no longer recognise your products as ads and would import them again. Going back to 0.9.173 is fine.
* Using Klassifieds Pro? It needs 0.9.154 or later, as since 0.9.172.

= 0.9.173 =

* Fixed: 0.9.172 stopped every page of the site with a fatal error, "Call to undefined function Klassifieds\klassifieds()". The plugin declared its main function only after loading the class that calls it. The error came before the plugin changed anything, so the 0.9.172 update never started: this version carries it out on its first request, copying tables, meta, label colours and shortcodes to their new names.
* Everything 0.9.172 says applies. Using Klassifieds Pro? Update it to 0.9.154 or later first.

= 0.9.172 =

* Every name the plugin adds to WordPress now carries its own prefix, as WordPress.org asks: its database tables, the meta keys on products and images, the colour of a label, its shortcodes, its cron schedules, its script and style handles, the REST namespace, a cookie and two transients. Many of them used to start with `ka`, which is too short to be unique.
* Your data comes along. On the first request after the update, the plugin copies its tables, the meta of products and images and the colours of labels to the new names, and renames `[ka-timestamp]` and `[ka-classifieds-total]` to `[klassifieds-timestamp]` and `[klassifieds-total]` in posts, pages and widgets. Until the copy has finished, no import or sync job runs. The old tables and meta stay untouched for now, so going back to 0.9.171 still works; a later version removes them.
* Using Klassifieds Pro? Update it to 0.9.154 first. Pro 0.9.153 and older stop with a fatal error against this version.
* Using one of the shortcodes in a theme template, through `do_shortcode()`? Rename it there; only content and widgets are updated for you.
* For developers: `KA()` is now `klassifieds()`. The product classes `WC_Product_Klassified` and `WC_Product_Variable_Klassified` are now `Klassifieds_Product_Simple` and `Klassifieds_Product_Variable`. The REST namespace `ka/v1` is now `klassifieds/v1`, with no alias. Script and style handles are `klassifieds-*` instead of `ka-*`, and the constant `WC_TERMS` is `KLASSIFIEDS_WC_TERMS`. Query the tables through `$wpdb->klassifieds_meta_lookup`, `$wpdb->klassifieds_quarantine` and `$wpdb->klassifieds_deleted_posts`, and read the meta through the `KLASSIFIEDS_META__*` constants.

= 0.9.171 =

* The plugin carries no ad capacity and enforces no licence any more. The code Klassifieds Pro used to hold an install to its licensed number of ads is gone: the helpers that picked the ads a licence covers, a second check after the plugin's own filters, the capacity notice on the settings screen and the dot on the dashboard's ad count. Klassifieds Pro 0.9.153 does all of it itself. What stays are three filters, `klassifieds_index_limit`, `klassifieds_task_items` and `klassifieds_pre_create_ad_product`, and each passes on what its callbacks answer.
* Using Klassifieds Pro? Update it to 0.9.153 first. Pro 0.9.152 and older stop with a fatal error against this version.
* A limit set through `klassifieds_index_limit` is kept exactly. The account index used to hold up to one page of ads more than the limit asked for.
* For developers: `Klassifieds\Enums\Exception::CAPACITY_REACHED` is now `Exception::DEFERRED`, with the same value. A refusal from `klassifieds_pre_create_ad_product` carrying it still makes the import job skip the ad silently.

= 0.9.170 =

* The admin scripts and stylesheets that ship pass WordPress's JavaScript and CSS linters (`wp-scripts lint-js` and `lint-style`). Their configuration ships as `eslint.config.cjs` and `stylelint.config.cjs`. The admin screens look the same.
* Fixed on the way, found by the linters: saving a product from the list threw on an undefined variable; deleting a product's images never noticed a missing post ID; a failed save of the terms of use threw in its error handler instead of showing the error; and the onboarding screen could fail on a page without the plugin's AJAX script.
* The browser console stays quiet: the heartbeat, job, screen and version diagnostics print only with `WP_DEBUG` on. Failures are still reported there as errors.
* Saving the General settings while the account installer is running is refused with "Cleanup in progress…" only while the previous account really is being cleaned up, and no longer while inactive ads are being removed.
* Code that never ran is gone: three demo components nothing loaded, one of them a copied snippet under CC BY-SA, and a style rule that matched no element.

= 0.9.169 =

* The plugin ships the sources of its compiled parts: `src/` for the onboarding screen and the two Tailwind inputs for the admin stylesheet, together with `package.json` and its lock file. The new "Source code and building" section says how to rebuild them. `composer.json` carries the plugin's name and description and no longer maps a namespace no class uses.
* Whether a product still needs its images is asked per mode. Where images are stored, the thumbnail has to be a stored file; with "Link images from kleinanzeigen.de" ticked, a linked or a stored image will do, so images already stored stay — their files go when "Cleanup regularly" on the Tools tab removes their product. In either mode an image that is neither is fetched again. The setting's description says so.
* Four yes/no settings are read the same way whether they were saved as text or as a number: the terms-of-use confirmation, publishing new products directly, the inactive-ad cleanup, and whether an administrator or shop manager has opted in to a notification.
* Klassifieds Pro: 0.9.152 stops discarding stored images on every sync, which kept the dashboard's "missing images" count changing and downloaded every image again. It works with this version and with 0.9.168, so the order of the updates does not matter.

= 0.9.168 =

* Security: the public REST routes under `ka/v1/products` serve published products only. The list also returned drafts and trashed products, and a single product was served whatever its status — both without logging in. A draft or a trashed product now answers like a missing one, to anyone, an administrator included. Changing and deleting a product still ask what the user may do, and still reach drafts.
* Ad images are stored in your media library, as WebP, and your shop serves them itself. Until now they were linked from kleinanzeigen.de, so every visitor's browser fetched them there and sent kleinanzeigen.de the visitor's IP address, user agent and your shop's address — which the readme said never happens. After updating, a background job stores the linked images one product at a time; until a product's turn comes, its images still load from kleinanzeigen.de. A site that would rather keep the images off its disk can tick "Link images from kleinanzeigen.de instead of storing them" on the General tab, which says what that means for visitors. Downloads ask for WebP: without that, the image host answers with a PNG about thirty times larger.
* The German translation keeps the names. The plugin stays "Klassifieds for WooCommerce" instead of becoming "Kleinanzeigen für WooCommerce", and "Klassifieds" no longer turns into "Kleinanzeigen" anywhere. Where a text means the platform, it names kleinanzeigen.de, in English as well — the terms-of-use confirmation among them. Every text is translated, those added in 0.9.167 included, and two that said something other than the English are fixed: the refusal to show the sync status said you were allowed, and the thank-you line had lost its link.
* The label colour uses WordPress's own colour picker. The bundled wp-color-picker-alpha 3.0.1, an outdated copy of a third-party library, is gone, and with it a transparency slider no label used.
* The plugin no longer changes WordPress's cron lock for the whole site: it set `WP_CRON_LOCK_TIMEOUT` to 30 seconds, which applied to every plugin's scheduled events. When it asks for its background work to start, WordPress's own `spawn_cron()` sends the request — only when something is due, respecting the cron lock, and verifying SSL as the site's settings say, where the plugin used to switch verification off. A site with `DISABLE_WP_CRON` gets no such request.
* The admin screens' SVG images carry no `<style>` elements any more. The logo and installer animations are styled by a stylesheet loaded on the plugin's own screens, the two SVG files by attributes. Nothing looks different.
* The admin screens' requests are answered through `wp_send_json()`. The plugin's own error handler used to run on every `wp_die()`, which would have put an error after every such answer; it now answers real errors only.
* The General tab lists Scheduling last, and the installer's mode label is set smaller.
* Klassifieds Pro: update it to 0.9.151 before this version. Pro's debug statistics and the inline editor's term count read the plugin's answers as text; 0.9.151 reads them either way.

= 0.9.167 =

* Security: the plugin no longer logs anyone in by itself. It hooked into WordPress's authentication for every request to the site and signed in the user whose ID and encrypted application password stood in the request's URL, and its REST routes under `ka/v1` accepted nothing else — not even a logged-in administrator. Authentication is now WordPress's own, a login cookie with its REST nonce or an application password in the Authorization header, and the routes only ask what that user may do. A `redirect_url` parameter that forwarded the response to any address is gone with it. The product update route now also reads a JSON body.
* Security: every admin action asks whether you may do it before anything else. Removing or syncing the account, rebuilding the lookup table or the taxonomies, and saving the settings checked only that the request came from the plugin's own form.
* Security: the label colour and the brand fields are saved only from their own form, by someone allowed to edit that term, and only those fields. The check meant for this looked for a form field that was never sent, so it never ran, and any posted value was stored as term meta, for a term of any taxonomy. The colour takes hex or rgb()/rgba(), the brand images take attachment IDs, and saving hangs on the plugin's own two taxonomies only. A new label also keeps the colour picked for it instead of getting the default.
* Security: the settings import checks the upload itself — PHP's own error code, that the file is this request's upload, its extension, its size on disk and its type — and refuses the whole file when one entry does not fit the section it is imported into. A labels export can no longer be read into categories, and a label's action is limited to the choices the settings screen offers. A successful import now says so.
* Every setting has a sanitizer of its own: the account ID, each checkbox (stored as "1" or ""), the keyword lists. Two buttons on the Tools tab and two fields of the import form were registered as settings, so saving wrote them into the options table; they no longer are.
* Removed a logged-out AJAX action that handed out nonces nothing could use.
* Klassifieds Pro: update it together with this version. The "Publish now" link in Pro's new-product e-mail relied on the login removed here; the matching Pro release replaces it with a signed link, and the `[ka-rest-message]` shortcode of the page it opens now comes with Pro.

= 0.9.166 =

* Fixed: the debug log was never written. With `WP_DEBUG_LOG` on, the plugin wrote to a `logs` folder under uploads that nothing ever created, so every line was lost. It now writes to `wp-content/uploads/klassifieds-for-woocommerce/ka-logs/`, which is created on first use with its own `index.html` and `.htaccess`. The SQL log, written when `SAVEQUERIES` is on, no longer ends in a fatal error when its file cannot be opened.
* Removed the admin notices code the plugin had taken over from WooCommerce. Its notices had the same names as WooCommerce's, and so did the user meta that remembers a dismissed notice, so dismissing a notice in either plugin hid the other's. The code that displayed them loaded a file that does not exist, and none of its notices was ever shown. The plugin now stores no user meta at all.
* Removed `Utils::_log()`, which nothing called and which replaced the day's log file instead of adding to it.

= 0.9.165 =

* Fixed: deleting the plugin could end in a fatal error. `uninstall.php` loaded a file that does not exist, and tried to load the whole plugin at a point where it is already deactivated and WooCommerce may be gone. Uninstall now runs from its own class, `Klassifieds_Uninstaller`, which loads nothing of the plugin.
* Fixed: uninstall could delete other plugins' data. It removed every option and user meta key matching `%klassifieds_%` — a leading wildcard, and an underscore that SQL reads as any character — and it deleted orphaned term relationships, terms and term meta across the whole site. It now removes only names that start with `klassifieds_`, matched literally, and deletes terms only by the IDs of the plugin's own taxonomies. Klassifieds Pro's options, its licence among them, are left to Pro's own uninstall.
* Uninstall now removes what it used to leave behind: the plugin's scheduled events and its folder under uploads, on every site of a network. Products of the plugin's own product types become WooCommerce simple and variable products, so they keep working afterwards. Shop content — products, their Klassifieds data, labels, and the "specials" and "rent" attributes — stays, unless `KLASSIFIEDS_REMOVE_ALL_DATA` is set to `true` in `wp-config.php`.
* Activation no longer changes WooCommerce's shop manager role. Earlier versions set 26 capabilities WooCommerce gives shop managers — editing and publishing posts and pages, managing categories, moderating comments, export and import among them — to false, so activating the plugin quietly took those rights away. On the first request after updating, each site gets back every one of them that is still denied, once; a capability you have changed yourself since is left as it is. Uninstall does the same.

= 0.9.164 =

* Seven of the eight bundled font families are gone. Gibson, Good Times, Good Times Bad Times, Adobe Handwriting Ernie and Venganza were licensed through Adobe Fonts, which does not permit self-hosting; Google Sans and Google Sans Text are Google's own brand faces and are not licensed to anyone else at all. Five of the seven were referenced by nothing, and Roboto — the one that could have stayed — only ever appeared as a fallback *name* in the mail and front-end stylesheets, which needs no bundled copy. The two that were really in use are replaced in kind, each under the SIL Open Font License with its licence beside it: the installer's mode label takes Orbitron, which also renders the weight that rule asks for where the old bundle had none and the browser was synthesising one; and the Get-PRO panel takes Herr Von Muellerhoff, a calligraphic script chosen against the original for stroke weight and slant, sized so the line keeps the width and the line box the panel was drawn around. The package sheds 26 files and 1.11 MB.
* The plugin no longer decides the typeface of the whole admin. Tailwind's preflight sets `font-family` on `html` rather than on the plugin's own markup, so on a Klassifieds screen this stylesheet was choosing the font for the admin menu, the toolbar and every notice WordPress renders. It uses the stack wp-admin declares for itself now, so the plugin's pages stop re-setting the admin's typography.

= 0.9.163 =

* Fixed: the settings import read its file with `unserialize()`. A file uploaded through the import form was handed to PHP's object deserializer, which builds objects out of whatever the file describes and runs their `__wakeup()` and `__destruct()` while doing it — and with WooCommerce loaded that is a wide surface. What the plugin did with the result afterwards made no difference: the work happens inside the call. Exports are JSON now and imports are read with `json_decode()`, which cannot construct an object at all. Settings files written by an earlier version are no longer accepted — export again from the site they came from.
* Removed the settings framework's `value_type` argument and the `serialize()` behind it. A field declaring `serialized` had its stored value serialized into the rendered input's `value` attribute, submitted back as that string and stored raw — so `get_option()` unserialized a string the browser had sent, every time it was read. No field ever declared it and the save path never knew it existed. Neither `serialize()` nor `unserialize()` appears anywhere in the plugin now.
* Import and export ask whether the user may act, not only where the request came from. Both had a nonce and no capability check, and `render_settings()` runs on `admin_init` for anyone who reaches wp-admin. Both now require the plugin's own capability — the one every AJAX handler was put behind at 0.9.161, and which this pair was missed out of.
* The import now checks the file it was actually handed: `is_uploaded_file()` before anything reads it, the size measured from the file on disk instead of the reported field, and the decoded contents required to be an array before they are passed on. A comment claimed the `is_uploaded_file()` check was made further down; it was made nowhere.
* Fixed: the exported file was run through `wp_kses_post()`, which strips markup out of the payload. Because PHP serialization carries the byte length of every string, any mapping containing an angle bracket exported as a file that could not be read back in.
* Fixed: the export sent its headers after the body, so on an installation without an active output buffer the `Content-Disposition` was ignored and the file rendered in the browser instead of downloading.
* Fixed: importing keyword categories read `description` without checking that it was there, and put it and the term name into `wp_insert_term()` unnarrowed. Entries that are not arrays are skipped now, and both fields are sanitized first.
* Fixed: the import branch was gated by the *export* filter, so an extension narrowing `klassifieds_export_settings_{tab}` switched importing off as well. Importing has its own `klassifieds_import_settings_{tab}` now.
* Fixed: an installation reported itself at capacity when it has none. The dashboard's capacity endpoint compared the ads managed against a capacity of `null`, and PHP reads `null` as zero in that comparison, so the answer was "reached" everywhere. Nothing was visibly capped — the notice and the snackbar are held back by the empty message that arrives with it — but the `ka::capacity-reached` cookie was written on every poll, and a single message filter would have turned that into a limit users could see.
* The capacity no longer registers a veto that cannot veto. Creating an ad asks the capacity again immediately after `klassifieds_pre_create_ad_product` has run, on the same object, so the callback on that filter could only repeat the answer it was about to give. The filter itself is untouched and still open to extensions.
* Removed the machinery for an ad allowance this edition does not have: the grandfathered floor with its stored option, and two licence-cache stubs that only threw. Every installation had been writing an option named `capacity_floor` that nothing ever read back.
* Licence and capacity wording throughout. A comment announcing "Ads the free edition manages" had outlived the constant it described and reattached itself to the quarantine lifetime. The refusal message said "This edition manages %d ads", where no edition here manages a number at all. Two docblocks spoke of a "licensed capacity" in code that runs in every edition.
* The account notice names the account type it actually means. "This software only supports Klassifieds Pro Accounts" meant the commercial seller account on kleinanzeigen.de, not the paid add-on that happens to share the name; it now says so, and says that ads from a private account cannot be imported. The greyed-out checkbox beside it is gone — it was permanently ticked, could not be changed, and no code read it.
* A German translation file for a text domain the plugin has not used since 0.9.135 is no longer packaged. Nothing loaded it, 281 of its 300 entries are in the file that is loaded, and none of the remainder is a string the plugin still has.
* Fixed: the stable tag named 0.9.161 while the package was 0.9.162.

= 0.9.162 =

* Every link in the plugin points at the plugin site rather than the company root, which is under construction: the Author URI in the header, five docblock links, and the trigger service's disclosure in this readme.
* The trigger service's terms and privacy policy are linked properly. The terms link went to a page carrying an Impressum and no terms at all; both now point at the real documents, in English.

= 0.9.161 =

* The free edition no longer manages a fixed number of ads. It was 50, enforced through the crawl limit, a task trim and a veto on creation — which is trialware under the WordPress.org guidelines, whatever the number. `capacity()` answers null, every gate stands down, and the licence wording is gone from this package. Klassifieds Pro enforces the capacity its licence was sold with, through the same helpers.
* The plugin no longer defines `DISABLE_WP_CRON`. It was set for the whole site, which stopped scheduled events for every other plugin installed alongside it — and it is what made the external trigger service load-bearing rather than optional. WordPress's own scheduler runs the jobs now.
* The trigger service is opt-in and off by default. Nothing is sent unless it is switched on under Klassifieds → Settings; activation no longer registers, and switching it off withdraws the registration. It sends the site address, the plugin version and the edition, once a day, and the plugin works without it.
* Fixed: the REST endpoints for updating and deleting a product accepted any authenticated user, with no capability check and no check that the product was theirs. Both ask `user_can()` now, and their filters can only narrow that answer.
* Fixed: the REST endpoints answered for any product that had *ever* been imported, because `_ka_internal_id` stays on a product for good. Membership is `_ka_active` now — what the install manages today.
* Every AJAX handler is behind the plugin's own capability. A nonce proves a request came from one of our screens; on a shop, every customer can obtain one.
* Fixed: `update_term_meta()` was writing both key and value straight from the request.
* Fixed: a job could run twice at once. The guard compared a transient against the string `'1'`, but an object cache hands the value back as an integer, so on every install with Redis or Memcached the guard reported no active job while one was running.
* Nothing is written inside the plugin folder any more. Logs and the fetch cache live under `wp-content/uploads/`, created with an index.html and a .htaccess. The plugin folder is replaced on every update and is read-only on many hosts.
* Output escaping and input sanitizing across the admin, the includes and the front end: 112 findings to none.
* `defined( 'ABSPATH' ) || exit;` in the eighteen files that lacked it.
* Ten of the thirteen inline `<script>` and `<style>` blocks are enqueued files now. Two of them shipped a copy of the same function for every row of a list. The three that remain are inside inline SVG, where they belong to the image.
* The admin menu sits at position 58, after the WooCommerce group, instead of 10 — the slot Media occupies.
* jetpack-autoloader 3.1.3 → 6.0.0, jetpack-constants 2.0.5 → 4.0.0.
* Fixed: an unused fallback in the onboarding logo referenced a remote image. It could never render, and only shipped the address.
* The readme describes what is imported and how, and documents both external services with their conditions and links.

= 0.9.160 =

* A local development configuration file is no longer packaged. It was read by the development environment only and had no part in the plugin at runtime.

= 0.9.159 =

* The "capacity reached" signal carries a message, not just a yes or no. It travels as an object now — the answer and the text that goes with it — so the snackbar can say *why* the limit was hit instead of only that it was. New `ajax_capacity` action, answered by `Klassifieds_Dashbord::get_capacity()`.
* Renamed throughout, from "max reached" to "capacity reached": the `ka:capacity-reached` and `ka:capacity-reached:change` events, the `ka::capacity-reached` cookie and the `.capacity-reached` class. The old names described a ceiling; what is actually being reported is a capacity, and the two are not the same thing.
* The shop summary tolerates missing values instead of assuming they are there, so a summary rendered before its counts have arrived shows zeroes rather than notices.

= 0.9.158 =

* The dot that opens the capacity message on the shop summary is legible on both grounds: it takes a border in the dark logo colour and an outline in the light one, so the ring stays visible whichever the header is set to. It is 15 pixels across and was previously lost against one of the two.

= 0.9.157 =

* The header logo is no longer one fixed colour scheme. Its fills come from two custom properties, `--logo-dark-color` and `--logo-light-color`, and the two admin screens set them the other way round: the dashboard keeps the violet mark on a light ground, the settings screen inverts it.
* `--vendor-light-color` is a faint violet (`#f6eeff`) rather than pure white, so the light half of the pair belongs to the same palette as the dark half.
* The dashboard's mode indicator follows the logo pair instead of `--mode-bg-color`, so it inverts with the screen it sits on.
* Its own version, because the plugin looks different while behaving identically — two packages that differ only in appearance must not share a version number.

= 0.9.156 =

* The admin header carries the new Klassifieds logo. It takes its colour from the `--vendor-dark-color` custom property instead of a hard-coded red, and brings its own animation with it.
* That property is violet (`#7300ff`) now, so the change is not limited to the logo: `--mode-bg-color` is derived from it and colours the dashboard's mode indicator too.
* Its own version, because the plugin looks different while behaving identically — two packages that differ only in appearance must not share a version number.

= 0.9.155 =

* `plugin-check.json` is no longer packaged into the plugin zip. It is a wp-env configuration for the plugin-check tool, pointing at localhost zips, and the only thing that reads it — the `wp plugin-check` scripts in `package.json` — is already excluded, so nothing shipped referred to it. No runtime change.

= 0.9.154 =

* The standalone tests are no longer packaged into the plugin zip (`/tests/ export-ignore`). They stub WordPress and run anywhere PHP does, which is the point of them — but they belong to development, not to a customer's server. No runtime change; only the contents of the zip differ, which is why this gets its own version rather than rebuilding 0.9.153 with different contents under the same number.

= 0.9.153 =

Note: the free edition still had an ad capacity when this shipped. It was removed
in 0.9.161, so the machinery described below bounds Klassifieds Pro today — the
free edition has no capacity for it to enforce.

* The capacity is now enforced where its numbers are *used*, not only through the filters that answer them. `klassifieds_task_items`, `klassifieds_pre_create_ad_product` and `klassifieds_index_limit` are filters, so a callback registered at a later priority had the last word on all three and could raise an install past what its licence covers with one `add_filter()` in a theme. Each consumer now asks the capacity again after the filter has run and combines the two answers with an operator that can only narrow: `min()` for the crawl limit, a re-filter for a task's items, and — a permission having no `min()` — asking again for the create veto. Narrowing an install from a filter still works exactly as before; only widening stops.
* New `Klassifieds_Capacity::set_authority()` / `bound()`: one non-filter way to ask who owns the capacity on this install. `capacity()` stands down under Pro and answers `null` there, which is the one answer a bound must never guess. First writer wins, so the claim cannot be taken back. Klassifieds Pro 0.9.145 claims it.
* New `Klassifieds_Capacity::confine_task_items()`, the single definition of the narrowing rule, applied by `Klassifieds_Task::items()`. `gate_task_items()` keeps answering the filter — that is what bounds an install whose task class predates the call — and keeps `invalid-ad`'s widening, which stays a filter because losing it costs a retirement cycle and not a slot.

= 0.9.152 =

Note: as above — the capacity these entries refine is Klassifieds Pro's now. The
free edition's own was removed in 0.9.161.

* The account index is never larger than the capacity. The limit was the capacity plus a page of reserve, and because pages arrive whole the crawl then stopped a page past that — a 120-ad capacity held 300 ads. The limit is now the capacity exactly and the fetched pages are trimmed to it.
* Consequence worth knowing: `entitled_ids()` drops quarantined ads before slicing, so without the reserve an ad that cannot be fetched costs a slot for good — five unfetchable ads in a 120-ad window mean 115 products.
* Capacity retirement no longer requires a complete index. It is the overage now, not membership, and a number a truncated index cannot inflate; requiring completeness would have switched retirement off permanently, since a capped index is truncated by design on any account larger than the capacity.

= 0.9.151 =

* The account modal collects the terms of use itself. Entering an account ID could never succeed while an agreement was unticked — the installer refuses the ID until `terms_of_use_confirmed` is `1` — but the modal showed the field anyway and let the user walk into a refusal that explains itself nowhere. The field is now disabled until the agreements hold, and the agreements are on the same screen instead of behind a link to the settings.
* Fixed: saving with an unticked agreement took the whole request down. The agreements are registered as strings but default to `false`, so `/wp/v2/settings` reports an unset one as `null` and then refuses that same `null` on the way back (`rest_invalid_stored_value`). The values are normalised to `"1"` or `""` before they are sent.
* Fixed: a listener in the account modal was added on every render and never removed.
* The terms-of-use view can be left on any save, not only on the one that completes it.
* German translations updated, including the script translations for the modal.

= 0.9.142 =

* Ad pages are now read through a list of candidate selectors instead of a single element id, so imports keep working when kleinanzeigen changes the page layout.
* Fixed: no images were imported when the gallery markup changed, because the fallback selector was never reached.
* Fixed: a changed address layout aborted the import with an uncaught error.
* Fixed: reading the account ID from an ad URL failed with a fatal error.
* Fixed: ads whose description could not be read stored "Not found" as the product description. The stored description is kept now and the ad is retried later.
* Fixed: umlauts could be garbled when reading ad pages.
* Ad page parsing moved into its own class, `Klassifieds_Vip_Document`.

== Upgrade Notice ==

= 0.9.180 =
Plainer wording on the General tab, and a fix for the image-import message in German.

= 0.9.179 =
Products whose ad comes back no longer stay drafts on busy accounts. Publish any that already did by hand.

= 0.9.178 =
Switching on the trigger service registers your site right away. Update Klassifieds Pro to 0.9.161 first.

= 0.9.177 =
Reads your whole account instead of only its first page of ads.

= 0.9.176 =
Maintenance: no shell commands, and debug output only with WP_DEBUG_LOG.

= 0.9.175 =
Security hardening: the product list's save request accepts only a few sanitized fields.

= 0.9.174 =
Removes the plugin's data under its old names, which 0.9.173 copied. Afterwards,
do not go back to 0.9.171 or older: it would import your ads again.

= 0.9.173 =
Fixes a fatal error on every page in 0.9.172, and carries out its update. Using
Klassifieds Pro? Update it to 0.9.154 or later first.

= 0.9.172 =
Gives the plugin's tables, meta keys and shortcodes its own prefix and copies your
data over on the first request. Using Klassifieds Pro? Update it to 0.9.154 first;
older versions stop with a fatal error.

= 0.9.171 =
Removes the capacity and licence code Klassifieds Pro relied on. Using Klassifieds
Pro? Update it to 0.9.153 first; older versions stop with a fatal error.

= 0.9.170 =
Fixes errors in the admin screens that WordPress's linters found, and keeps the
browser console quiet unless WP_DEBUG is on.

= 0.9.169 =
Ships the sources of its compiled files. Using Klassifieds Pro? Update it to
0.9.152, which stops stored images from being discarded on every sync.

= 0.9.168 =
Stores ad images in your media library instead of linking them from
kleinanzeigen.de, and keeps drafts out of the public REST API. Using Klassifieds
Pro? Update it to 0.9.151 first.

= 0.9.167 =
Security fixes: no more login from URL parameters, and every admin action, setting
and import is checked. Using Klassifieds Pro? Update it together with this version.

= 0.9.166 =
Fixes debug logging, which never wrote a line, and removes admin notice code
taken over from WooCommerce that mixed its dismissed notices with WooCommerce's.

= 0.9.165 =
Fixes uninstall, which could fail with a fatal error and delete other plugins'
data, and gives shop managers back the rights earlier versions removed from
their role.

= 0.9.164 =
Appearance and packaging. The admin screens leave WordPress's own typeface alone
instead of replacing it, and the bundled fonts that could not be redistributed are
gone. Nothing behaves differently.

= 0.9.163 =
Security fix: the settings import no longer reads its file with unserialize(),
and importing and exporting settings now check that the user may do so. Note the
one breaking change — settings files exported by an earlier version are no longer
accepted, so export again from the site they came from. Also fixes an
installation reporting itself at capacity when it has none. Recommended for every
installation.

= 0.9.157 =
Appearance only: the header logo takes its colours from a pair of custom
properties that the dashboard and the settings screen set in opposite order, so
the two screens carry the mark inverted from each other. Nothing behaves
differently.

= 0.9.156 =
Appearance only: the admin header carries the new logo and takes its colour from
a custom property, which is violet now. Nothing behaves differently.

= 0.9.155 =
Packaging only: a wp-env configuration file no longer travels in the plugin zip.
Nothing on your site behaves differently.

= 0.9.154 =
Packaging only: the standalone developer tests no longer travel in the plugin
zip. Nothing on your site behaves differently.

= 0.9.153 =
The capacity is now enforced where its numbers are used, so a filter registered
at a later priority can still narrow an installation but no longer widen it past
what its licence covers. (The free edition's own capacity was removed in 0.9.161;
what this bounds today is Klassifieds Pro.) Recommended for every installation.
