=== GerSync Exchange for 1C ===
Contributors: vigilansnoctis
Donate link: https://buymeacoffee.com/ger.igor
Tags: 1c, commerceml, woocommerce, import, synchronization
Requires at least: 6.4
Tested up to: 7.1
Requires PHP: 8.1
WC requires at least: 8.5
WC tested up to: 11.2
Stable tag: 1.5.0
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

GerSync: free two-way exchange between 1C:Enterprise and WooCommerce using the standard CommerceML 2 protocol: products, prices, stock and orders.

== Description ==

The plugin implements the standard "exchange with website" protocol built into 1C configurations
(Trade Management, Small Business Management, Integrated Automation, etc.). Nothing needs to be modified on the 1C side.
A Russian version of this document is in readme-ru.txt.

**Catalog (1C → website)**

* Category tree from 1C groups, products, descriptions, SKUs, weight, images.
* 1C properties become global WooCommerce attributes (usable in filters).
* 1C characteristics ("Id#Id") become variable products with variations.
* Prices: a chosen price type plus an optional "sale" price type (by name or Id).
* Stock: summed across all warehouses; CommerceML 2.05 and 2.08+ (offers / prices / rests).
* Products marked for deletion in 1C are moved to drafts or trash and restored with their previous
  status when the mark is removed. Products you removed manually are never touched.
* Large catalogs are imported in steps, so hosting timeouts are not a problem.

**Orders (website → 1C)**

* Orders are sent to 1C after creation and after every status change, including shipping and fees.
* Optional: order statuses from 1C (cancelled, shipped, paid) are applied back to the website.
* Compatible with HPOS (WooCommerce high-performance order storage).

**Speed**

* Products whose data in 1C has not changed since the previous exchange are skipped; prices and stock are saved
  only when they actually change. A repeated exchange of an unchanged catalog takes seconds.
* Images are uploaded once: identical files are recognised, including images uploaded by the previous
  1C exchange plugin (no duplicates after switching).
* Optional "WooCommerce sizes only" mode generates fewer thumbnail files and uploads images 2–3× faster.
* "Forced full exchange" (Maintenance tab) reprocesses every product once on the next exchange.
* "Image cleanup" removes images uploaded by the exchange that are no longer used by any product.

**Other**

* Picks up GUIDs left by the previous 1C exchange plugin (`_id_1c` meta), so switching does not create duplicates.
* Polylang / WPML: when translations share a GUID, the product or category in the default language is updated;
  the exchange URL also works with a language prefix (/en/wc1c-exchange/).
* Per-field update control for existing products: name, description, categories, images, SKU, weight, prices and stock
  can each be protected from being overwritten by 1C.
* A "Do not update from 1C" checkbox on a product's Inventory tab protects a hand-edited product
  (prices and stock still update).
* Attributes: "sync with 1C", "only add values from 1C, never remove" or "don't touch".
  Attributes added manually (not coming from 1C properties) are never removed in any mode.

**Full export and changes-only export**

* The export type is detected automatically from the 1C file ("contains only changes" flag).
* Changes-only export: only the received products are updated, nothing else is touched.
* Full export: products from 1C that are missing in it can be left as is, unpublished (draft),
  set to "Out of stock" or moved to trash. Products created manually on the website are never affected.
* Runs in the background (WooCommerce Action Scheduler) after 1C signals the end of the exchange,
  or 15 minutes after the last full file for configurations that do not send the signal.
* Safety: if more than half of the 1C products are missing, nothing is done and a warning is logged
  (looks like an interrupted exchange). A hidden product comes back as soon as it appears in an export again.
* Translations (Polylang / WPML) follow the original product.

**Security**

* The exchange password is stored only as a hash; after login a random session token is used.
* 10 wrong passwords lock the IP for 15 minutes; an optional IP allowlist is available.
* Temporary files live in a randomly named folder protected by .htaccess / web.config.
* Strict file name checks (only xml / zip / images, no "..", no symlinks); zip-slip and zip-bomb protection.
* XML is parsed as a stream without network access or external entities (XXE-safe).
* Statuses from 1C are applied only to orders the website itself sent to 1C.
* Internal errors are written to the WooCommerce log only and are never shown to the client.

== Installation ==

1. In WordPress go to **Plugins → Add New → Upload Plugin**, choose the zip file and click **Install Now**, then **Activate**.
   WooCommerce must be installed and active.
2. Go to **WooCommerce → Обмен с 1С** (the GerSync settings page). Set an exchange **login** (Latin letters) and a long random **password**, then save.
3. Copy the **exchange URL** shown at the top of the settings page, e.g. `https://example.com/wc1c-exchange/`.
4. In 1C open **Administration → Data exchange → Exchange with websites** (the exact path depends on the configuration),
   create an exchange node and enter the URL, login and password from step 2–3.
5. Choose what to exchange in 1C: products (catalog, prices, stock) and/or orders. Run the exchange.
6. If products already exist on the website and have no 1C GUID, enable **"Match existing products by SKU"**
   before the first exchange to avoid duplicates.

It is strongly recommended to run the first exchange on a copy of the website or after making a database backup.

**If 1C reports that the server did not receive the login/password**

On some hosts (PHP running as CGI/FastCGI) the Authorization header does not reach PHP.
For Apache add this to `.htaccess` in the site root, before the WordPress rules:

`RewriteEngine On`
`RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]`

For nginx + PHP-FPM add: `fastcgi_param HTTP_AUTHORIZATION $http_authorization;`

**nginx note:** .htaccess is ignored by nginx. Deny direct access to the temporary exchange folder in the server config:

`location ~* /gersync-[a-f0-9]{24}/ { deny all; return 404; }`

The "Connection" tab of the settings page checks automatically whether the folder is reachable from the web.

**CDN / page cache note:** exchange responses are sent with `Cache-Control: no-store`, but a CDN rule like Cloudflare "Cache Everything" with an Edge TTL override ignores it. Exclude the exchange address (`/wc1c-exchange/` or `?wc1c_exchange`) from such rules (Cloudflare: Cache Rules → Bypass cache).

== Security ==

* The exchange password is stored only as a hash (minimum 12 characters, built-in generator); after login 1C gets a random session token bound to its IP address. Changing the password revokes all sessions.
* Optional "HTTPS only" mode: requests over plain HTTP are refused before the password is checked.
* One password check at a time per network and a 1-second pause after a wrong password; lockout for 15 minutes after 10 wrong passwords per address, with additional limits per network and for the whole site. Optional allowlist of IP addresses and networks (CIDR); exact allowlisted addresses are never locked out.
* Behind Cloudflare or a reverse proxy, the real client IP is taken from the header you choose, and only for proxies you list as trusted.
* Uploaded file names are strictly validated: only xml/zip/images, no `..`, no double extensions such as `x.php.jpg`, no `.htaccess` / `.user.ini`, no symlinks. Archives are protected against path traversal, zip bombs and nested archives.
* Images are verified by their real content (type must match the extension, size and pixel limits). Images that contain PHP code which PHP would actually compile (checked with the PHP parser, for both short_open_tag settings) are re-encoded or rejected before they reach the media library.
* XML is parsed as a stream without network access and without external entities (XXE).
* Product names, SKUs, property names and values from 1C are stored as plain text; descriptions pass through wp_kses_post.
* Exchange responses are never cached and never contain PHP error messages; anonymous requests cannot flood the log.
* The settings page shows a security checklist: HTTPS, IP allowlist, web access to the temporary folder, last 1C login, failed logins.

== Frequently Asked Questions ==

= Where can I see the 1C GUID of a product? =

In the admin product editor, on the **Inventory** tab, and inside each variation. It is never shown on the storefront.

= Where are the logs? =

WooCommerce → Status → Logs, source `gersync`. Enable "detailed log" in the plugin settings for step-by-step entries.

= What happens if I uninstall the plugin? =

Settings and temporary files are removed. Products, categories and their 1C links stay, so reinstalling will not create duplicates.

= Is it really free? =

Yes. All features work without payment and nothing is locked. If the plugin saves you time, you can buy the author
a coffee via Buy Me a Coffee (link on the settings page). The link is shown only in the plugin's own admin pages.

= How do I update without losing settings? =

Use the normal WordPress update (Plugins → Update). Settings, products and their 1C links are kept.
If you update manually with a zip file, choose "Upload Plugin" and then "Replace current with uploaded" —
do not delete the plugin first: deleting removes its settings (products and 1C links still stay).

== Trademarks ==

1C and 1C:Enterprise are trademarks of 1C Company. WooCommerce is a trademark of Automattic Inc.
GerSync is an independent plugin and is not affiliated with, endorsed or sponsored by 1C Company or Automattic.

== Upgrade Notice ==

= 1.5.0 =
Security hardening release. Recommended for all users. If your exchange password is shorter than 12 characters it keeps working, but a new password must be at least 12 characters.

= 1.4.0 =
Internal names now use the `gersync` prefix. Settings and 1C links from earlier versions are migrated automatically on update.

= 1.3.0 =
Much faster repeated exchanges. The first exchange after updating processes all products once (images are not re-uploaded); later ones skip unchanged products.

== Changelog ==

= 1.5.0 =
* Security: uploaded files with dangerous names (double extensions like `x.php.jpg`, `.htaccess`, `.user.ini`) are rejected.
* Security: images are checked by content before import: the type must match the extension, images over 40 megapixels are rejected, and images containing anything that looks like PHP code are re-encoded (or rejected if that fails).
* Security: nested archives are not extracted; size and free disk space are checked before unpacking.
* Security: session bound to the client IP and to the current credentials (password change logs 1C out), maximum lifetime 24 hours.
* Security: constant-time login check, one password check at a time per network, 1 s delay after a wrong password; failed-login limits per address and per network (IPv4 /24; IPv6 /64, /56, /48) plus a site-wide limit (it never blocks the addresses of recent successful 1C logins); successful logins do not count against the limits; exact allowlisted IPs have their own higher limit, so others cannot lock your 1C out.
* Security: X-Forwarded-For parsing stops at entries it cannot parse (no spoofing through "unknown" or address:port values); malformed IP list entries never match; proxy addresses are not accepted in the IP allowlist; the settings page warns when the site seems to be behind a proxy/CDN that is not configured.
* Security: stronger exchange passwords (at least 12 characters, no login or common words) with a built-in generator; the settings page asks to replace passwords set before 1.5.
* Security: optional "HTTPS only" mode — exchange requests over plain HTTP are refused before the password is checked; enabled by default on new installs on HTTPS sites.
* Security: PHP error messages are never shown in exchange responses, including fatal errors (like WordPress does for REST API and AJAX).
* Security: the settings page shows the last 1C login (time and IP) and the number of failed logins in the last hour.
* Security: optional trusted proxies for the real client IP, with an explicit choice of header (X-Forwarded-For or Cloudflare's CF-Connecting-IP).
* Security: product names, SKUs, properties, characteristics, price types and categories from 1C are stored as plain text; negative and infinite prices are ignored.
* Security: responses marked as non-cacheable for page-cache plugins, LiteSpeed and CDNs; PHP warnings never leak into responses.
* Security: protection files of the temporary folder updated (Apache 2.2/2.4, IIS) and rewritten if outdated; self-check of web access with an nginx rule on the settings page.
* Reliability: parallel import of the same step is prevented by an atomic lock; products missing from a full exchange are processed only after 15 minutes of exchange inactivity.
* Security: free disk space and a total size limit are checked before accepting files; order statuses from 1C are matched by order ID (by number only if the site's order number matches).
* Reliability: much faster search for products missing from a full exchange on large catalogs; a product that failed to import is not treated as missing; products moved to the trash manually are not restored by the exchange; image cleanup also keeps category images, site logo/icon and images attached to posts.
* Uninstall removes all temporary records of the plugin.

= 1.4.0 =
* Unique `gersync` prefix for code, options and metadata; automatic migration from 1.0–1.3.
* Output escaped at the point of output; exchange request parameters read and sanitized individually.
* Explicit nonce and capability check for the per-product "Do not update from 1C" checkbox.
* Settings backup with automatic restore.

= 1.3.0 =
* Ready for WordPress.org: name "GerSync Exchange for 1C", slug and text domain `gersync`, admin assets enqueued, passes Plugin Check.
* Data versioning with automatic migrations on update; uninstall keeps settings if another copy of the plugin is installed.
* Fixed: exchange passwords containing quotes or backslashes were rejected on some servers.
* Optional "buy the author a coffee" and author page links (admin only).
* Unchanged products are skipped; no-op saves of prices and stock are avoided.
* Images uploaded by the previous plugin are reused; optional WooCommerce-only thumbnail sizes.
* Forced full exchange with a warning; background cleanup of unused exchange images.
* Responses to 1C in Windows-1251 (no garbled messages in the 1C log).
* New settings page: status summary and tabs (Connection, Catalog, Prices & stock, Orders, Advanced, Maintenance).

= 1.2.0 =
* Full / changes-only export handling with an action for products missing from a full export.
* Update protection for SKU, weight, prices and stock; per-product "Do not update from 1C" checkbox.
* Attribute modes: sync / add only / don't touch; manual visibility and order of attributes are preserved.
* Translations are hidden and restored together with the original product.

= 1.1.0 =
* Price types from the classifier in offers.xml (CommerceML 2.08+).
* Products and variations are restored when the deletion mark is removed in 1C.
* Fees are included in exported orders; statuses from 1C apply only to exported orders.
* Polylang / WPML support; exchange URL no longer depends on rewrite rules.
* 1C GUID shown for variations in the admin; import code split into modules.

= 1.0.0 =
* First release.
