=== Klassifieds for WooCommerce ===
Contributors: bartcc
Tags: woocommerce, kleinanzeigen, classifieds, product import, sync
Requires at least: 6.7
Tested up to: 7.1
Requires PHP: 8.1
Stable tag: 0.9.187
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Turn the classified ads of a kleinanzeigen.de account into WooCommerce products and keep them in sync.

== Description ==

Klassifieds for WooCommerce imports the classified ads of a kleinanzeigen.de
account into your shop. Your ad becomes a WooCommerce product carrying its
title, description, price, images and location, and can be brought back in step
with its ad whenever you ask: prices follow, changed texts follow, and an ad that
has disappeared upstream is taken out of the catalogue instead of being sold on.

The only credential the plugin needs is the **public account ID** — the one that
appears in the account's own kleinanzeigen.de URL. There is no login, no OAuth
and no password to store.

Products appear as soon as their text data has been read; their images follow
within a few minutes, fetched by a background job.

= What it does =

* Imports ads as WooCommerce products of their own product type — a simple
  and a variable classified product, so ads with variants stay variable.
* Brings products back in step with their ads when you start a sync from the
  plugin's dashboard: new ads are created, changed ads are updated, and ads that
  have vanished upstream are deactivated and fall back to plain products rather
  than being deleted.
* Restores a product automatically when its ad comes back.
* Keeps the shop tidy: a product whose ad has been gone for more than 30 days
  is deleted, together with its images. This housekeeping is the "Cleanup
  regularly" setting on the Tools tab, on by default.
* Maps ad content to your shop: category, label and keyword mapping is
  configured in the plugin's settings.

= Images are stored in your media library =

Each ad image is downloaded once, as WebP, and stored in your media library like
an image you upload yourself, so your shop serves it from its own address. A
typical photo takes about 30 KB, plus the sizes WordPress makes of it.

A site can keep the images off its disk instead: tick "Show images from
kleinanzeigen.de instead of copying them" on the plugin's General settings tab.
Its product pages then load every image from kleinanzeigen.de, so each visitor's
browser sends kleinanzeigen.de the visitor's IP address, their browser's user
agent and your shop's address. If you switch it on, say so in your privacy
policy. The `klassifieds_hotlink_images` filter overrides the setting.
Ticking it applies to images fetched from then on: the ones already stored stay
in your media library until their product is removed, which the "Cleanup
regularly" setting on the Tools tab (on by default) does once its ad has been
inactive for more than 30 days. Unticking it again stores the linked images in
the background, one product at a time; until a product's turn comes, its images
still load from kleinanzeigen.de.

= How the work is scheduled =

Imports and syncs run in the background through WordPress's own scheduler
(WP-Cron), which advances when your site receives traffic. On a shop that is
still quiet, that can be slow and irregular. There are two ways to make it
steady, neither of them required:

* Set up a system cron for `wp-cron.php` — the usual approach for any WordPress
  site that cares about timing.
* Or switch on the optional trigger service described below.

= External services =

This plugin contacts two third-party services. Both are called from your server,
never from a visitor's browser, unless you switch on hotlinking (see "Images are
stored in your media library" above). One is required for the plugin to do
anything at all; the other is optional and switched off by default.

**kleinanzeigen.de** — the source of the ads and the reason the plugin exists.
The plugin requests the public account page and the public ad pages of the
account ID you enter, reads the ad data from them and downloads the ad images.
What is sent is the account ID and the ad IDs, as part of the URL of an ordinary
HTTP request; no data about your shop, your customers or your visitors is
transmitted.

Requests happen while an import or sync you started is running, and from the
background job that downloads the images of any product still missing them. Once
the items an import brought in are in the store — their text data and their
images alike — no further requests are made until you
manually trigger a sync from the plugin's dashboard page.

Terms of use: https://themen.kleinanzeigen.de/nutzungsbedingungen/
Privacy policy: https://themen.kleinanzeigen.de/datenschutzerklaerung/

**pinger.webpremiere.de** — an optional trigger service run by the plugin
author, for shops whose own traffic is too thin to drive WordPress's
scheduler reliably. It is switched off by default and nothing is sent unless you
turn it on under **Klassifieds → Settings**.

When you switch it on, and then once a day while it is on, your server sends the
service one request. It carries your site address, the plugin version, whether
the free or the Pro edition is running, and a fixed token that marks the request
as coming from this plugin (the same in every copy, so it identifies nothing).
Like every web request, it also passes your server's IP address, the time, and
the user agent WordPress sends, which names the WordPress version and your site
address. Beyond that, nothing is sent: no content of your shop or your ads, and
no data about your customers or your visitors.

The service then requests `wp-cron.php` on your site about once a minute, which
lets WordPress run its scheduled work on time. Those requests carry no data.

The service keeps one entry per site: the site address, the plugin version, the
edition and the time of the last registration. It does not keep the IP address.
The entry is deleted as soon as you switch the service off or deactivate the
plugin, and automatically once a site has not registered for four days. The
server's access log, which records every request with its IP address, is
deleted after seven days. The plugin author sees the list of registered sites
in order to run the service; it is used for nothing else and passed on to no
one. The service runs on the author's own server in Germany. To have your entry
removed, or to ask what is stored about your site, write to info@webpremiere.de.

The plugin works without it — imports and syncs simply advance whenever
WordPress's own scheduler runs.

Terms: https://klassifieds.webpremiere.de/en/agb/
Privacy policy: https://klassifieds.webpremiere.de/en/datenschutz/

= Source code and building =

Two parts of the plugin are compiled, and their sources ship with it:

* `build/`, the onboarding screen, is built from `src/` with
  `@wordpress/scripts`.
* `admin/css/style-admin-klassifieds.css` is built with Tailwind CSS from
  `admin/css/style-admin-klassifieds-input.css`, which imports
  `admin/css/style-admin-common-input.css`.

To rebuild them, run these in the plugin's folder, with Node.js and npm installed:

* `npm install`
* `npm run build` rebuilds `build/` from `src/`
* `npm run tw:build` rebuilds `admin/css/style-admin-klassifieds.css`

The PHP libraries in `vendor/` are installed with Composer, from `composer.json`
and `composer.lock`: `composer install --no-dev --optimize-autoloader`.

= Klassifieds Pro =

A paid add-on runs the synchronization unattended, so products follow their ads
without anyone starting a run, and adds a distributed sync worker, screenshots,
e-mail reports and discount pricing. It is not required and it unlocks nothing
here — everything described above works on its own.
See https://klassifieds.webpremiere.de

== Installation ==

1. Install and activate WooCommerce. Klassifieds for WooCommerce does not run
   without it.
2. Upload the plugin to `/wp-content/plugins/`, or install it through the
   Plugins screen in WordPress.
3. Activate the plugin through the Plugins screen.
4. Open **Klassifieds → Settings**, read and confirm the terms of use.
5. Enter your kleinanzeigen.de account ID. The import starts by itself a few
   seconds later and runs in the background.

Requirements: WordPress 6.7 or newer, PHP 8.1 or newer, WooCommerce.

== Frequently Asked Questions ==

= Where do I find my account ID? =

Open your kleinanzeigen.de profile page. The account ID is the number in that
page's address. It is public — it is not a login and not a password.

= Does the plugin need my kleinanzeigen.de password? =

No. The plugin reads public pages only and stores no credentials for
kleinanzeigen.de.

= Why is my import going slowly? =

Background work runs through WordPress's scheduler, which only advances when
your site receives traffic. On a new shop with few visitors that can be slow and
irregular. Set up a system cron for `wp-cron.php`, or switch on the optional
trigger service under **Klassifieds → Settings**.

= Are the ad images copied into my media library? =

Yes, by default, as WebP: a typical photo takes about 30 KB, plus the sizes
WordPress makes of it. To keep them off your disk, tick "Show images from
kleinanzeigen.de instead of copying them" on the General settings tab; your
visitors' browsers then load them from kleinanzeigen.de (see "Images are stored
in your media library" above).

= What happens when an ad is deleted on kleinanzeigen.de? =

Its product is deactivated and falls back to a plain WooCommerce product. It is
not deleted, and its previous state is kept, so the product is restored if the
ad reappears. If the ad stays gone for more than 30 days, the "Cleanup
regularly" setting on the Tools tab (on by default) deletes the product for good.

= Can I edit an imported product by hand? =

Yes, but a later sync will bring the fields that come from the ad back in line
with the ad. Fields the plugin does not own — your own categories, labels and
the mapping you configured — are left alone.

= What does the plugin remove when I uninstall it? =

Its own database tables, options and scheduled events, and its folder under
`wp-content/uploads/`. Products created from ads stay in your shop as ordinary
WooCommerce products, and so do their ad data, the labels and the "specials"
and "rent" attributes. To remove those too, add this line to `wp-config.php`
before you delete the plugin:

`define( 'KLASSIFIEDS_REMOVE_ALL_DATA', true );`

The products themselves are never deleted.

== Screenshots ==

1. The dashboard counts every product the plugin manages, and one button brings them all back in step with their ads.
2. The import runs as a background job: it reports its progress and carries on after you leave the page.
3. Setup asks for the public account ID and nothing else — no login, no password, no OAuth.
4. Imported ads are WooCommerce products of their own type, each with a link to its ad and actions to re-import it, fetch its images, detach it or deactivate it.

== Changelog ==

= 0.9.187 =

* Prices: a product whose ad has no price keeps an empty price instead of 0, and the price is set on the product itself before its data store saves it. The lookup table's price difference can be adjusted through the new `klassifieds_has_price_diff` filter.

= 0.9.186 =

* In affiliate mode, Klassifieds products count as purchasable, with or without a price; up to 0.9.185 they did not. The button keeps linking to kleinanzeigen.de and reads "Buy at" or "Visit at" by the price. In normal mode a product without a price shows no button, as before.

= 0.9.185 =

* In affiliate mode, the button of a product without a price reads "Visit at kleinanzeigen.de" instead of "Buy at kleinanzeigen.de". Products with a price keep "Buy at kleinanzeigen.de".
* For developers: the unused function `klassifieds_add_to_cart_text()` is gone. The `klassifieds_affiliate_button_text` filter receives either text.

= 0.9.184 =

* Products the plugin creates for new ads get their description, price, keyword categories and labels, and brands again. Since 0.9.160 the ad page was read with two arguments swapped, so every product the create job made came out without them; a manual import was not affected. The ad page is read as it was in 0.9.141 again, and the class `Klassifieds_Vip_Document` is gone. To fill in the products created since then, run "Rebuild product data" on the Tools tab once.
* The dashboard keeps updating when you decide to stay on a page that asked before you left it (Klassifieds Pro asks while a bulk edit runs).
* For developers: the e-mail tab's helpers for Klassifieds Pro (`statusReport`, `parseCaptureSize`, `randomProductData`, `screenshot`) and the `SCREENSHOT_API` value are no longer in `admin/js/ajax.js`; Klassifieds Pro 0.9.166 carries its own. With Pro 0.9.165 the e-mail tab's two buttons do nothing, so update Pro to 0.9.166.

= 0.9.183 =

* Security: posts by users without the `unfiltered_html` capability no longer keep `<script>` and `<style>` elements. The plugin's filter on WordPress's allowed post HTML let both through, on every page of the site; it now adds only an SVG with its path and two attributes.
* Security: the product list's actions — importing and deleting images, featuring, connecting, detaching, deleting, restoring and changing the status — check that you may edit, delete or publish that very product, not only that you may use the plugin, and no longer reach posts of other types.
* The readme says exactly what the optional trigger service receives, keeps and deletes, and for how long. The privacy policy and terms it links describe the service as well.
* The readme's changelog keeps the latest releases; the older ones are in `changelog.txt`, which ships with the plugin.
* For developers: `Klassifieds_Exception` extends `Exception` without declaring `Throwable` itself, and the admin scripts fit Prettier's line width again. Klassifieds Pro needs no update.

= 0.9.182 =

* The admin screens' markup, styles, data attributes and JavaScript events carry the plugin's prefix: `klassifieds-` instead of `ka-` in class names, ids and `data-` attributes, and `klassifieds:` instead of `ka:` in the events. Update Klassifieds Pro to 0.9.164 at the same time: until both are updated, its product list's actions and search notice don't react.
* The log folder is `wp-content/uploads/klassifieds-for-woocommerce/logs/`. An existing `ka-logs/` folder is left as it is.
* For developers: the admin body classes are `klassifieds-free` and `klassifieds-pro` (`Enums\Constant::FREE`, `::PRO`), the location icon of an ad from kleinanzeigen.de has the class `kleinanzeigen`, and its image is `admin/images/icon_kleinanzeigen.svg`.

= 0.9.181 =

* Installing and updating finishes, or tries again. The plugin records its version only once its tables and settings are in place, and checks that its tables exist after creating them; if a step fails, the next request runs them all again. Every other request reads a single setting.
* The update steps for installs of 0.9.173 and older are gone: the data move of 0.9.172 and the shop_manager repair. A site still on one of those versions updates to 0.9.180 first.
* Uninstalling no longer looks for data under names from before 0.9.172.
* Needs Klassifieds Pro 0.9.162. Update Pro first: this version ends Pro 0.9.161 and older in a fatal error.
* For developers: every name carries the plugin's prefix. The product accessors are `get_klassifieds_*()` and `set_klassifieds_*()` only; `get_klassifieds_product()`, `is_klassifieds_pro()` and `is_klassifieds_free()` replace their short-named versions; the REST API's `ka_price` field is `klassifieds_price`; error codes and schedule names are `klassifieds_*`. Values both plugins read are enums (`Schedules`, `Connector`, the new `Cache`), and the dashboard's top header is filled through the `klassifieds_dashboard_topheader` action.

Older releases are listed in changelog.txt, which ships with the plugin.

== Upgrade Notice ==

= 0.9.187 =
Products without a price keep an empty price instead of 0. Klassifieds Pro needs no update.

= 0.9.186 =
Affiliate mode: Klassifieds products count as purchasable. Klassifieds Pro needs no update.

= 0.9.185 =
In affiliate mode, products without a price offer to visit the ad instead of buying it. Klassifieds Pro needs no update.

= 0.9.184 =
Products created for new ads get their price, categories, labels and brands again. Run "Rebuild product data" on the Tools tab once to fill in the ones made since 0.9.160. Using Klassifieds Pro? Update it to 0.9.166.

= 0.9.183 =
Security fixes: posts no longer keep script from contributors, and the product list's actions check your rights on each product. Recommended for every installation.

= 0.9.182 =
Update Klassifieds Pro to 0.9.164 at the same time.

= 0.9.181 =
Update Klassifieds Pro to 0.9.162 first. A site still on 0.9.173 or older updates to 0.9.180 first.

