=== Caravel Sync for WooCommerce ===
Contributors: caravelsuite
Tags: woocommerce, product import, csv, xml, stock sync
Requires at least: 6.5
Tested up to: 7.1
Requires PHP: 7.4
Requires Plugins: woocommerce
Stable tag: 1.0.0
License: GPL-2.0-or-later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Import and update WooCommerce products from CSV, XML or JSON feeds, with a dry run, approval, price rules, change history and revert.

== Description ==

Keep your catalog in line with a supplier or ERP feed. Each import profile reads one feed (a web address or an uploaded file) and creates or updates products by SKU.

* **Any number of profiles.** CSV, XML and JSON feeds, from an http(s) address or an uploaded file.
* **Field mapping.** SKU as the key; name, short description, regular price, sale price, cost, stock, categories (with sub-categories), images, and attributes for simple variations (rows with a parent SKU become variations of a variable product).
* **Rules.** Price from the feed or from the cost plus a margin, rounding (cents, whole number, .99, 0.05), minimum price, stock held back, and filters ("only import records where…").
* **Dry run first.** A dry run lists every change (before → after) without touching the shop. The first import of each profile must be approved.
* **Safety stops.** If a price would change by more than the percentage you set, the import pauses until someone approves it or stops it.
* **Change history and revert.** Only changed fields are written and recorded. Any kept import can be reverted: old values come back and products it created are removed. Fields edited after the import are left alone. The number of runs kept per profile is a setting.
* **Built for large feeds on shared hosting.** Runs in background batches with WooCommerce's Action Scheduler. Feeds are read as a stream (CSV by byte offset, XML node by node, JSON element by element), each batch has a time and memory budget and saves its position, unchanged products are skipped by a hash without being loaded or saved, and images are downloaded in their own queue.
* **Native WordPress screens** under WooCommerce > Product sync, in English, Portuguese (Portugal), Spanish, French, German and Italian.
* HPOS compatible. Messages are written to the WooCommerce log (source "csync").

= Connections to other servers =

This plugin connects only to the addresses **you** enter in your profiles, to download your feed files, and to the image addresses listed inside those feeds, to download product images. Requests identify themselves with a User-Agent that contains the plugin name and your site address. No data about your shop or your customers is sent anywhere, and there is no tracking.

= Security =

* Only http and https on the standard ports (80 and 443). Every redirect and every image address is checked again.
* Addresses that resolve to private, internal or reserved networks (including cloud metadata addresses such as 169.254.169.254, IPv4 and IPv6) are refused, and the connection uses the address that was checked.
* XML files with a DOCTYPE or ENTITY declaration are refused (no external entities, no entity expansion); the network is disabled for the XML parser.
* Downloads stop when they pass the size limit. Feed files are saved with random names in a private folder that cannot be opened from the web.
* Every screen and action requires the "manage WooCommerce" permission, and every change requires a security token.

= For developers =

Other code can extend the plugin without changing it:

* `csync_init` (action, with the `Runner`) runs once the plugin is running. `Runner::start( $profile_id, 'live', $label )` starts an import (approval rules still apply), `cancel()` and `revert()` are also available.
* `csync_source_types` (filter) and `csync_fetch_source` (filter) add a feed source type and fetch it into the private file given.
* `csync_profile_form_after` (action) prints extra sections in the profile form; `csync_sanitize_profile_extra` (filter) sanitizes and stores them; `csync_profile_saved` and `csync_profile_deleted` (actions).
* `csync_admin_tabs` (filter) and `csync_admin_tab_{slug}` (action) add a tab to the plugin page.
* `csync_run_finished` and `csync_run_suspended` (actions).
* `csync_cost_meta_key` (filter) changes the meta key used for the cost.
* `csync_allowed_private_hosts` (filter) allows named hosts on your own network (empty by default).

More at [caravelsuite.com](https://caravelsuite.com/).

== Installation ==

1. Install and activate WooCommerce 8.0 or later (WordPress 6.5 or later, PHP 7.4 or later).
2. Upload caravel-sync.zip under Plugins > Add New > Upload Plugin and activate it.
3. Open WooCommerce > Product sync and add a profile: the feed address or file, the format, and which feed field fills each product field.
4. Click "Save and read the field names from the feed" to get a list of the fields in your feed.
5. Run a dry run, check the changes and approve the first import.

On multisite, activate it on each site separately.

== Frequently Asked Questions ==

= How are products matched? =

By SKU. Records without a SKU are skipped. A record with a parent SKU becomes a variation of the variable product with that SKU, which is created if needed.

= Which fields are changed? =

Only the fields you map. Fields left empty in the mapping are never changed, and a field is only written when its value is different.

= What happens when a price changes a lot? =

If a regular or sale price would change by more than the percentage set in the profile (30% by default), the import pauses at that product. The run page lists the price waiting for approval. You can approve these changes (the import pauses again at the next product with a large change), approve every large change for the rest of that import, or stop it. A sale price that starts or ends counts against the price customers paid before. An empty or unreadable price in the feed never removes a product's price.

= What does a revert do? =

It restores the old value of every field the import changed and removes the products it created (to the trash; created variations are deleted). If someone edited a field after the import, that field keeps the newer value. Only the runs that are still kept (see Settings) can be reverted; imports that changed the shop are kept separately from dry runs, so dry runs never push them out.

= Does it run on a schedule? =

Runs are started from the profile screen. The plugin's actions let other code start runs, for example on a schedule.

= What does the plugin delete when I delete it? =

Its own tables (profiles, runs, change history), its settings, its pending background actions and its private folder with feed files. Products, categories and images that were imported stay in your shop.

== Screenshots ==

1. Profiles: every feed, its approval and its last run.
2. A profile: feed address or file, field mapping, rules and options.
3. A dry run: every change it would make, before and after, waiting for approval.
4. Runs: imports, dry runs and reverts with their results.
5. Settings and the list of connections the plugin makes.

== Changelog ==

= 1.0.0 =
* First version: import profiles for CSV, XML and JSON feeds, field mapping, price and stock rules, filters, dry run with approval, pause on large price changes, change history with revert, background batches and an image queue.

== Upgrade Notice ==

= 1.0.0 =
First version.
