Plugin Directory

Changeset 3762744


Ignore:
Timestamp:
10/24/2026 04:45:14 AM (5 hours ago)
Author:
koalabtech
Message:

Version 1.3.0

Location:
koalab-sync/trunk
Files:
1 added
4 edited

Legend:

Unmodified
Added
Removed
  • koalab-sync/trunk/includes/movements.php

    r3629219 r3762744  
    2222const KOALAB_SYNC_COLLAPSE_THRESHOLD = 1000; // Above this in one request → single "bulk" summary.
    2323const KOALAB_SYNC_DELIVER_CHUNK      = 200;   // Events per delivery batch.
    24 const KOALAB_SYNC_FIELDS             = array( 'regular_price', 'sale_price', 'stock_quantity', 'stock_status' );
     24/**
     25 * «This product does not keep an inventory count», said with a word.
     26 *
     27 * When `manage_stock` is switched off WooCommerce empties `stock_quantity` (`validate_props()`), so the
     28 * change used to travel as «12 → (nothing)», which Koalab cannot tell apart from a value nobody knows and
     29 * dropped as noise. Since 1.3.0 the plugin says it explicitly, in both directions, with the same token
     30 * Koalab uses everywhere else (`UNTRACKED_STOCK` in the API). Koalab 14 Oct 2026 also reads the old form.
     31 */
     32const KOALAB_SYNC_UNTRACKED          = 'untracked';
     33/**
     34 * The product fields whose changes travel to Koalab.
     35 *
     36 * Scalar only, and on purpose. A movement is a ledger line — «this went from X to Y» — and it is
     37 * read in a table next to hundreds of others. A full description or a gallery has no readable
     38 * before/after in that shape, so those changes belong to the catalogue reconciliation, not here.
     39 *
     40 * `sku` is first for a reason: it is the key Koalab matches products by. Changing it in the store
     41 * silently detaches the product from its catalogue entry, and until now nothing said so.
     42 */
     43const KOALAB_SYNC_FIELDS             = array(
     44        'regular_price',
     45        'sale_price',
     46        'stock_quantity',
     47        'stock_status',
     48        'sku',
     49        'name',
     50        'status',
     51        'manage_stock',
     52        'weight',
     53);
    2554
    2655/* -------------------------------------------------------------------------
    … …  
    167196 * ---------------------------------------------------------------------- */
    168197
     198/**
     199 * Flattens a field value into something a ledger line can hold.
     200 *
     201 * `manage_stock` arrives as a real boolean and `(string) false` is an empty string — which reads as
     202 * «the value was removed» instead of «it was off». Anything that is not a scalar is refused
     203 * outright rather than serialised into noise.
     204 */
     205function koalab_sync_scalar( $value ) {
     206        if ( is_bool( $value ) ) {
     207                return $value ? 'yes' : 'no';
     208        }
     209        if ( is_null( $value ) || is_scalar( $value ) ) {
     210                return $value;
     211        }
     212        return null;
     213}
     214
    169215function koalab_sync_push_event( $product, $field, $old, $new ) {
    170216        if ( ! koalab_sync_is_connected() || koalab_sync_is_suppressed() ) {
    … …  
    172218        }
    173219        if ( (string) $old === (string) $new ) {
     220                return;
     221        }
     222        // A non-scalar that `koalab_sync_scalar` refused. Sending it would put `Array` in the ledger.
     223        if ( ! is_null( $old ) && ! is_scalar( $old ) ) {
     224                return;
     225        }
     226        if ( ! is_null( $new ) && ! is_scalar( $new ) ) {
    174227                return;
    175228        }
    … …  
    211264        $in_order   = ! empty( $GLOBALS['koalab_sync_ctx'] );
    212265
     266        if ( array_key_exists( 'stock_quantity', $changes ) ) {
     267                // This save already reports its stock: the `..._set_stock` hooks below must not report it again.
     268                $GLOBALS['koalab_sync_stock_seen'][ $product->get_id() ] = true;
     269        }
     270
    213271        foreach ( KOALAB_SYNC_FIELDS as $field ) {
    214272                if ( ! array_key_exists( $field, $changes ) ) {
    … …  
    219277                }
    220278                $old = isset( $data[ $field ] ) ? $data[ $field ] : null;
    221                 koalab_sync_push_event( $product, $field, $old, $changes[ $field ] );
    222         }
     279                $new = $changes[ $field ];
     280                if ( 'stock_quantity' === $field ) {
     281                        list( $old, $new ) = koalab_sync_stock_pair( $product, $data, $old, $new );
     282                }
     283                koalab_sync_push_event( $product, $field, koalab_sync_scalar( $old ), koalab_sync_scalar( $new ) );
     284        }
     285}
     286
     287/**
     288 * The stock's before/after, with «no inventory control» named instead of left blank.
     289 *
     290 * - Switched off: WooCommerce has just emptied the quantity → «N → untracked».
     291 * - Switched on, on a product that already existed: the quantity appears from nothing → «untracked → N».
     292 *   A brand-new product also starts from nothing, and that is not a switch, so it keeps its blank.
     293 */
     294function koalab_sync_stock_pair( $product, $data, $old, $new ) {
     295        $was_managed = ! empty( $data['manage_stock'] ) && 'no' !== $data['manage_stock'];
     296        $is_managed  = (bool) $product->get_manage_stock( 'edit' );
     297        if ( ! $is_managed && ( null === $new || '' === $new ) ) {
     298                $new = KOALAB_SYNC_UNTRACKED;
     299        }
     300        if ( $is_managed && ! $was_managed && ( null === $old || '' === $old ) && $product->get_id() ) {
     301                $old = KOALAB_SYNC_UNTRACKED;
     302        }
     303        return array( $old, $new );
    223304}
    224305
    … …  
    255336}
    256337
    257 /** Refund restock → gives old and new directly. */
    258 function koalab_sync_capture_restock_refunded( $product_id, $old_stock, $new_stock, $item_qty = 0, $order = null ) {
     338/**
     339 * Refund restock → gives old and new directly.
     340 *
     341 * WooCommerce calls it with `( $product_id, $old_stock, $new_stock, $order, $product )`. Up to 1.2.0 the
     342 * fourth argument was read as a quantity and the fifth as the order, so the order was never found and the
     343 * refund reached Koalab without its order number. Whichever of the two is the order is used.
     344 */
     345function koalab_sync_capture_restock_refunded( $product_id, $old_stock, $new_stock, $arg4 = null, $arg5 = null ) {
    259346        $product = wc_get_product( $product_id );
    260347        if ( ! $product ) {
    261348                return;
    262349        }
    263         koalab_sync_ctx_open( 'refund', is_a( $order, 'WC_Order' ) ? $order->get_id() : 0 );
     350        $order = is_a( $arg4, 'WC_Order' ) ? $arg4 : ( is_a( $arg5, 'WC_Order' ) ? $arg5 : null );
     351        // WooCommerce restocks a refund item by item and calls this after each one. Closing the context
     352        // here left the NEXT item's stock write without it, so the `..._set_stock` hooks would report it a
     353        // second time as a hand edit. The context the refund opened is restored instead.
     354        $prev_ctx   = isset( $GLOBALS['koalab_sync_ctx'] ) ? $GLOBALS['koalab_sync_ctx'] : null;
     355        $prev_order = isset( $GLOBALS['koalab_sync_order_id'] ) ? $GLOBALS['koalab_sync_order_id'] : 0;
     356        koalab_sync_ctx_open( 'refund', $order ? $order->get_id() : 0 );
    264357        koalab_sync_push_event( $product, 'stock_quantity', $old_stock, $new_stock );
    265         koalab_sync_ctx_close();
     358        if ( $prev_ctx ) {
     359                koalab_sync_ctx_open( $prev_ctx, $prev_order );
     360        } else {
     361                koalab_sync_ctx_close();
     362        }
     363}
     364
     365/**
     366 * Stock written straight to the database by `wc_update_product_stock()`.
     367 *
     368 * That function updates the quantity with SQL and then loads it into the product as already READ, not as
     369 * a pending change, so the save that follows has no stock change in it and the CRUD hook above saw
     370 * nothing. It is the path of WooCommerce's own bulk edit («Products → Bulk actions → Edit → Stock qty»),
     371 * of the order-item quantity edits in the admin, and of most ERP and stock-sync plugins. Up to 1.2.0
     372 * those changes never reached Koalab. Read in WooCommerce 11.2.1 (`wc-stock-functions.php`,
     373 * `class-wc-admin-post-types.php`).
     374 *
     375 * The same pair of actions also fires inside an ordinary save that does carry a stock change, and inside
     376 * every order operation: the first is already reported by the CRUD hook (`koalab_sync_stock_seen`), the
     377 * second by the order hooks (an open context). Both are skipped, so nothing is counted twice.
     378 */
     379function koalab_sync_capture_before_set_stock( $product ) {
     380        if ( ! is_a( $product, 'WC_Product' ) || ! empty( $GLOBALS['koalab_sync_ctx'] ) ) {
     381                return;
     382        }
     383        if ( ! empty( $GLOBALS['koalab_sync_stock_seen'][ $product->get_id() ] ) ) {
     384                return;
     385        }
     386        $GLOBALS['koalab_sync_stock_before'][ $product->get_id() ] = $product->get_stock_quantity( 'edit' );
     387}
     388
     389function koalab_sync_capture_set_stock( $product ) {
     390        if ( ! is_a( $product, 'WC_Product' ) ) {
     391                return;
     392        }
     393        $id = $product->get_id();
     394        unset( $GLOBALS['koalab_sync_stock_seen'][ $id ] );
     395        if ( ! isset( $GLOBALS['koalab_sync_stock_before'] ) || ! array_key_exists( $id, $GLOBALS['koalab_sync_stock_before'] ) ) {
     396                return;
     397        }
     398        $old = $GLOBALS['koalab_sync_stock_before'][ $id ];
     399        unset( $GLOBALS['koalab_sync_stock_before'][ $id ] );
     400        if ( ! empty( $GLOBALS['koalab_sync_ctx'] ) ) {
     401                return;
     402        }
     403        koalab_sync_push_event( $product, 'stock_quantity', $old, $product->get_stock_quantity( 'edit' ) );
    266404}
    267405
    … …  
    315453const KOALAB_SYNC_MAX_ATTEMPTS = 5;
    316454
     455/**
     456 * Schedules a retry of a batch.
     457 *
     458 * The batch itself is parked in an option and only its key travels as the action's argument. Action
     459 * Scheduler refuses arguments longer than 8,000 characters once JSON-encoded —it logs the exception and
     460 * schedules nothing— and one event is ~350 characters, so up to 1.2.0 the retry of any batch of more than
     461 * about twenty events was silently never scheduled: exactly the batches that matter (a sale with many
     462 * lines, a bulk edit) were lost when Koalab answered with a 5xx.
     463 */
    317464function koalab_sync_enqueue_delivery( $events, $attempt = 1, $delay = 0 ) {
    318         // Entrega inmediata (delay 0): acción ASÍNCRONA de Action Scheduler — corre vía loopback en el
    319         // shutdown, en segundos, SIN depender de WP-Cron (clave en tiendas de bajo tráfico). Los
    320         // reintentos usan una acción programada con backoff.
     465        $key = 'koalab_sync_retry_' . md5( wp_json_encode( $events ) . '|' . $attempt . '|' . microtime( true ) );
     466        update_option( $key, $events, false );
     467        $args = array( $key, $attempt );
     468
    321469        if ( $delay <= 0 && function_exists( 'as_enqueue_async_action' ) ) {
    322                 as_enqueue_async_action( 'koalab_sync_deliver', array( $events, $attempt ), 'koalab-sync' );
     470                as_enqueue_async_action( 'koalab_sync_deliver_parked', $args, 'koalab-sync' );
    323471                return;
    324472        }
    325473        $when = time() + (int) $delay;
    326474        if ( function_exists( 'as_schedule_single_action' ) ) {
    327                 as_schedule_single_action( $when, 'koalab_sync_deliver', array( $events, $attempt ), 'koalab-sync' );
     475                as_schedule_single_action( $when, 'koalab_sync_deliver_parked', $args, 'koalab-sync' );
    328476        } else {
    329                 wp_schedule_single_event( $when, 'koalab_sync_deliver', array( $events, $attempt ) );
    330         }
    331 }
     477                wp_schedule_single_event( $when, 'koalab_sync_deliver_parked', $args );
     478        }
     479}
     480
     481/** Runs a parked retry: takes the batch out of its option (once) and delivers it. */
     482function koalab_sync_deliver_parked( $key, $attempt = 1 ) {
     483        if ( ! is_string( $key ) || 0 !== strpos( $key, 'koalab_sync_retry_' ) ) {
     484                return;
     485        }
     486        $events = get_option( $key, null );
     487        delete_option( $key );
     488        if ( is_array( $events ) ) {
     489                koalab_sync_deliver( $events, (int) $attempt );
     490        }
     491}
     492add_action( 'koalab_sync_deliver_parked', 'koalab_sync_deliver_parked', 10, 2 );
    332493
    333494/**
    … …  
    387548        }
    388549}
     550// Kept for retries scheduled by 1.1.0/1.2.0 before the update, which carry the batch itself.
    389551add_action( 'koalab_sync_deliver', 'koalab_sync_deliver', 10, 2 );
    390552
    … …  
    406568                add_action( 'woocommerce_before_product_object_save', 'koalab_sync_capture_product_save', 20, 1 );
    407569                add_action( 'woocommerce_before_product_variation_object_save', 'koalab_sync_capture_product_save', 20, 1 );
     570
     571                // Stock written by `wc_update_product_stock()` outside an order: bulk edit, ERPs, other plugins.
     572                add_action( 'woocommerce_product_before_set_stock', 'koalab_sync_capture_before_set_stock', 20, 1 );
     573                add_action( 'woocommerce_variation_before_set_stock', 'koalab_sync_capture_before_set_stock', 20, 1 );
     574                add_action( 'woocommerce_product_set_stock', 'koalab_sync_capture_set_stock', 20, 1 );
     575                add_action( 'woocommerce_variation_set_stock', 'koalab_sync_capture_set_stock', 20, 1 );
    408576
    409577                // Order-driven stock: precise old→new + origin.
    … …  
    420588                        add_action( 'woocommerce_order_status_' . $status, $open_sale, 1, 1 );
    421589                }
    422                 add_action( 'woocommerce_order_status_cancelled', function ( $order_id ) {
    423                         koalab_sync_ctx_open( 'return', $order_id );
    424                 }, 1, 1 );
     590                // Every status in which WooCommerce gives a reduced order's units back
     591                // (`wc_maybe_increase_stock_levels`): cancelled, pending and —since WooCommerce 11.0— failed.
     592                foreach ( array( 'cancelled', 'pending', 'failed' ) as $status ) {
     593                        add_action( 'woocommerce_order_status_' . $status, function ( $order_id ) {
     594                                koalab_sync_ctx_open( 'return', $order_id );
     595                        }, 1, 1 );
     596                }
    425597                add_action( 'woocommerce_create_refund', function () {
    426598                        koalab_sync_ctx_open( 'refund', 0 );
  • koalab-sync/trunk/koalab-sync.php

    r3629219 r3762744  
    11<?php
    22/**
    3  * Plugin Name:       Koalab Sync — Bulk Price & Stock Updates for WooCommerce
     3 * Plugin Name:       Koalab Sync for WooCommerce
    44 * Plugin URI:        https://koalab.tech
    5  * Description:       Update prices and stock of your entire WooCommerce catalog from your supplier's Excel/CSV price list — with markup rules, preview and one-click revert.
    6  * Version:           1.1.0
     5 * Description:       Optional companion for Koalab Sync: sends your store's price and stock changes to Koalab in seconds, each with its cause. Koalab works without it, through WooCommerce's own webhooks.
     6 * Version:           1.3.0
    77 * Author:            Koalab
    88 * License:           GPLv2 or later
    … …  
    1313 * Requires Plugins:  woocommerce
    1414 * WC requires at least: 7.0
     15 * WC tested up to:  11.2
    1516 */
    1617
    … …  
    1920}
    2021
    21 define( 'KOALAB_SYNC_VERSION', '1.1.0' );
    22 define( 'KOALAB_SYNC_APP_URL', 'https://koalab.tech' );
     22define( 'KOALAB_SYNC_VERSION', '1.3.0' );
     23
     24/**
     25 * A dónde habla el plugin. Producción por defecto, que es lo que instala un comerciante.
     26 *
     27 * Se puede fijar antes desde `wp-config.php` para apuntarlo a QA o a una API local. Sin esta
     28 * guarda no se podía: el valor estaba escrito en duro, así que el plugin sólo sabía hablar con
     29 * producción y no había forma de probarlo de punta a punta sin editar su código. Definirlo fuera
     30 * además provocaba un aviso de PHP por redefinir una constante.
     31 *
     32 * No cambia nada para quien lo instala desde WordPress.org: si nadie lo define antes, vale lo mismo
     33 * que valía.
     34 */
     35if ( ! defined( 'KOALAB_SYNC_APP_URL' ) ) {
     36        define( 'KOALAB_SYNC_APP_URL', 'https://koalab.tech' );
     37}
    2338
    2439// Movement capture engine (P4) — optional store-side change tracking.
    2540require_once plugin_dir_path( __FILE__ ) . 'includes/movements.php';
     41// Heartbeat and «I am being switched off», while movement sync is connected (1.3.0).
     42require_once plugin_dir_path( __FILE__ ) . 'includes/signals.php';
    2643
    2744// Translations are loaded automatically by WordPress.org for plugins hosted there
    … …  
    4259register_activation_hook( __FILE__, function () {
    4360        update_option( 'koalab_sync_show_notice', 1 );
    44 } );
     61        koalab_sync_on_activate();
     62} );
     63
     64/**
     65 * On deactivation: tell Koalab, so it goes back to WooCommerce's own webhooks at once instead of waiting
     66 * for a heartbeat that will not come. See includes/signals.php.
     67 */
     68register_deactivation_hook( __FILE__, 'koalab_sync_on_deactivate' );
     69
     70add_action( KOALAB_SYNC_HEARTBEAT_HOOK, 'koalab_sync_heartbeat' );
     71add_action( 'init', 'koalab_sync_schedule_heartbeat' );
     72add_action( 'admin_init', 'koalab_sync_heartbeat_fallback' );
    4573
    4674/**
    … …  
    81109                        'domain'     => rawurlencode( home_url() ),
    82110                ),
    83                 KOALAB_SYNC_APP_URL . '/apps/sync/credentials'
     111                KOALAB_SYNC_APP_URL . '/channels'
    84112        );
    85113}
    … …  
    214242                                'utm_source' => 'wp-plugin',
    215243                        ),
    216                         KOALAB_SYNC_APP_URL . '/apps/sync/credentials'
     244                        KOALAB_SYNC_APP_URL . '/channels'
    217245                );
    218246                // External redirect to the Koalab app: whitelist its host so wp_safe_redirect() allows it.
    … …  
    237265                $redirect = add_query_arg( 'koalab_paired', $res['ok'] ? '1' : 'pending', $redirect );
    238266        } elseif ( 'disconnect' === $action ) {
     267                // Up to 1.2.0 Koalab never heard about this and kept ignoring WooCommerce's own webhooks.
     268                koalab_sync_signal_off( 'disconnected' );
    239269                koalab_sync_clear_connection();
     270                wp_clear_scheduled_hook( KOALAB_SYNC_HEARTBEAT_HOOK );
    240271                delete_option( 'koalab_sync_pair_nonce' );
    241272                $redirect = add_query_arg( 'koalab_disconnected', '1', $redirect );
    … …  
    279310                </h2>
    280311                <p style="font-size:14px;color:#50575e;max-width:620px;">
    281                         <?php esc_html_e( 'Capture the price and stock changes that happen directly in your store — sales, returns, manual edits and third-party plugins — so they all show up in Koalab\'s Movements screen. Koalab keeps working without this; the plugin just adds deeper tracking.', 'koalab-sync' ); ?>
     312                        <?php esc_html_e( 'Koalab keeps working without this: WooCommerce\'s own webhooks already tell it about sales and price and stock edits within minutes. With movement sync, every change made in your store arrives in seconds, labelled with its exact time and cause — sale, refund, hand edit, import or another plugin — and SKU, name, status and weight changes are recorded too.', 'koalab-sync' ); ?>
    282313                </p>
    283314
    … …  
    309340                                        <h3 style="font-size:1em;margin:0 0 8px;"><?php esc_html_e( 'Option A — Enter a code', 'koalab-sync' ); ?></h3>
    310341                                        <p style="font-size:13px;color:#50575e;margin:0 0 8px;">
    311                                                 <?php esc_html_e( 'In Koalab → your store → Movements, generate a connection code and paste it here.', 'koalab-sync' ); ?>
     342                                                <?php esc_html_e( 'In Koalab, go to Channels → Configure → Plugin tab, generate a connection code and paste it here.', 'koalab-sync' ); ?>
    312343                                        </p>
    313344                                        <form method="post" action="<?php echo esc_url( admin_url( 'admin.php?page=koalab-sync' ) ); ?>">
  • koalab-sync/trunk/readme.txt

    r3629219 r3762744  
    1 === Koalab Sync — Bulk Price & Stock Updates for WooCommerce ===
     1=== Koalab Sync for WooCommerce ===
    22Contributors: koalabtech
    3 Tags: bulk edit, prices, stock, woocommerce, excel
     3Tags: multichannel, inventory sync, stock sync, price sync, woocommerce
    44Requires at least: 6.0
    5 Tested up to: 7.0
     5Tested up to: 7.1
    66Requires PHP: 7.4
    7 Stable tag: 1.1.0
     7Stable tag: 1.3.0
    88License: GPLv2 or later
    99License URI: https://www.gnu.org/licenses/gpl-2.0.html
    1010
    11 Update prices and stock of your entire WooCommerce catalog from your supplier's Excel/CSV price list — with markup rules and one-click revert.
     11Optional companion for Koalab Sync: sends your store's price and stock changes to Koalab in seconds, each with its cause.
    1212
    1313== Description ==
    1414
    15 **Koalab Sync** connects your WooCommerce store to [Koalab](https://koalab.tech), a cloud tool that turns your supplier's price list into an updated catalog in minutes.
     15[Koalab Sync](https://koalab.tech) is a cloud service that keeps your product catalog in one place and syncs prices, stock and sale prices to your sales channels — your WooCommerce store, and also Mercado Libre, Shopify or Tiendanube if you sell there. It can also update the whole catalog from a supplier's Excel/CSV price list, with markup rules, a preview and one-click revert.
    1616
    17 If you run a store that resells products from distributors — hardware, auto parts, pharmacies, electronics, cosmetics — you know the routine: your supplier sends a new price list in Excel, and you spend hours updating products one by one, or fighting with CSV imports that break your catalog.
     17**You do not need this plugin to use Koalab Sync.** You connect your store from Koalab with WooCommerce's own authorization screen, and Koalab subscribes to WooCommerce's webhooks: your sales and your price and stock edits reach Koalab within a few minutes, with nothing installed in WordPress.
    1818
    19 Koalab Sync fixes that:
     19This plugin is an optional companion for two cases:
    2020
    21 * **Upload the Excel/CSV your supplier sends you** — no reformatting needed. Column names are detected automatically and products are matched by SKU or ID.
    22 * **Apply your profit margin automatically** — markup, rounding and min/max price rules per store.
    23 * **Preview every change before it happens** — see old price → new price for every product, with alerts for suspicious changes (price zero, sale price above regular price, sudden drops).
    24 * **Revert with one click** — every update creates a snapshot. If something goes wrong, undo it entirely.
    25 * **Schedule updates** — leave the update ready and let it run at night.
    26 * **Zero load on your site** — everything runs in Koalab's cloud through the official WooCommerce REST API. This plugin adds no queries, no cron jobs and no tables to your WordPress.
     21* **You want the detail.** With movement sync connected, each change made in your store reaches Koalab within seconds of the request that made it, labeled with its exact time and its cause — sale, return, refund, hand edit, REST API, import or scheduled sale. Changes to SKU, name, status, stock management and weight are recorded too, not only price and stock.
     22* **WooCommerce's notices are not reaching Koalab.** WooCommerce sends its webhooks from its task queue, which depends on WP-Cron. If WP-Cron is disabled on your site, or a security plugin blocks outgoing requests, Koalab only learns about your store with its periodic import. The plugin delivers changes itself at the end of each request, without waiting for WP-Cron (on PHP-FPM hosts), and retries when Koalab cannot be reached.
    2723
    28 **Free plan available** — connect 1 store and run up to 5 updates per month at no cost, no credit card required.
     24While movement sync is connected, Koalab stops using WooCommerce's webhooks for that store, so no change is counted twice. If you deactivate, delete or disconnect the plugin, it tells Koalab, and Koalab goes back to WooCommerce's webhooks on its own. The plugin also checks in once a day, so Koalab notices when it has stopped reporting for any other reason.
    2925
    30 = What this plugin does =
     26= What the plugin adds to your WordPress =
    3127
    32 This plugin adds a page under your WooCommerce menu that connects your store to Koalab using WooCommerce's official authorization flow (`wc-auth`). You approve access with one click — no copying API keys, and Koalab never sees your WordPress password. All the heavy lifting happens in the cloud.
     28* A page under **WooCommerce → Koalab Sync** with links to create your Koalab account or connect your store (through WooCommerce's official `wc-auth` authorization flow: you approve access with one click, and Koalab never sees your WordPress password).
     29* The optional movement sync described above, off until you pair the plugin with your store in Koalab.
     30
     31The plugin adds no database tables and does not write to your products: everything Koalab writes to your store goes through the WooCommerce REST API keys you authorized.
    3332
    3433= Disponible en español =
    3534
    36 Koalab está completamente disponible en español (y portugués). Sube la lista de precios de tu proveedor en Excel, aplica tu margen de ganancia automáticamente, revisa los cambios antes de aplicarlos y actualiza cientos de productos en minutos — con reversión de un clic si algo sale mal. Plan gratuito disponible: 1 tienda y 5 actualizaciones al mes, sin tarjeta de crédito.
     35Koalab Sync está disponible en español y portugués. Este plugin es opcional: sin él, los avisos de la propia WooCommerce ya traen a Koalab tus ventas y tus cambios de precio y stock en unos minutos. Con él llegan en segundos, con su hora exacta y su causa, y funciona también en tiendas donde esos avisos no salen.
    3736
    3837== External services ==
    … …  
    4342
    4443* **When you connect your store** (clicking the connect button): you are redirected to Koalab and your store's site URL (domain) is sent so Koalab can pair with your store. Access to your products is then granted through WooCommerce's official authorization flow (`wc-auth`), which issues REST API keys you explicitly approve.
    45 * **During synchronization** (performed by Koalab, not by this plugin): Koalab reads and writes your WooCommerce **products** (names, SKUs, prices, stock) through those authorized REST API keys. Koalab does not access your WordPress admin, customers or orders.
    46 * **Optional movement tracking** (off unless you enable it): when a product's price or stock changes in your store, the plugin sends the change event (product ID, SKU, changed field, old/new value and timestamp) to Koalab's API so it appears on Koalab's Movements screen.
     44* **During synchronization** (performed by Koalab, not by this plugin): Koalab reads and writes your WooCommerce **products** (names, SKUs, prices, stock) through those authorized REST API keys, and reads your store's status report to know your currency, your WooCommerce version and whether this plugin is active.
     45* **Store notifications** (performed by Koalab, not by this plugin): unless movement sync is connected and reporting, Koalab subscribes your store to WooCommerce's own webhooks for product updates, product deletions and orders, so it learns about sales and edits without waiting for its periodic import. From an order Koalab keeps only its number, status, dates and the products and quantities sold — never the buyer's name, address or contact details. Koalab does not access your WordPress admin.
     46* **Optional movement sync** (off unless you pair the plugin with your store in Koalab): when a product's price, stock, SKU, name, status, stock management or weight changes in your store, the plugin sends the change event (product ID, SKU, product name, changed field, old and new value, cause, order number when there is one, and timestamp) to Koalab's API so it appears on Koalab's Movements screen.
     47* **Connection check** (only while movement sync is connected): once a day, and when the plugin is deactivated, deleted or disconnected from its page, the plugin sends your Koalab store ID, your site URL, the plugin version and a timestamp, so Koalab knows whether the plugin is still reporting and can go back to WooCommerce's webhooks when it is not. No product, order or customer data is included.
     48
     49Every request the plugin makes to Koalab is signed with a secret shared only when you pair the plugin.
    4750
    4851This service is provided by Koalab. By connecting your store you agree to Koalab's Terms of Service (https://koalab.tech/terms) and Privacy Policy (https://koalab.tech/privacy).
    … …  
    5053== Installation ==
    5154
     55You do not need the plugin to connect your store: in Koalab, go to **Channels**, connect a channel and choose WooCommerce. Install the plugin only if you want movement sync:
     56
    52571. Install and activate the plugin from the WordPress plugin directory.
    53 2. Go to **WooCommerce → Koalab Sync**.
    54 3. Click **"Create free account & connect"** (or "I already have an account").
    55 4. Approve access on the WooCommerce authorization screen — that's it.
     582. In Koalab, go to **Channels → Configure** on your WooCommerce store, open the **Plugin** tab and generate a connection code.
     593. In WordPress, go to **WooCommerce → Koalab Sync**, paste the code under **Movement sync (optional)** and click **Connect** — or click **Open Koalab** and confirm the store there.
    5660
    5761Requirements: WooCommerce 7.0+, your store must be served over HTTPS, and pretty permalinks must be enabled (Settings → Permalinks).
    … …  
    5963== Frequently Asked Questions ==
    6064
    61 = Is it free? =
     65= Do I need this plugin to use Koalab Sync? =
    6266
    63 Yes — the free plan includes 1 store and 5 bulk updates per month, with no credit card. Paid plans add more updates, scheduled syncs, price rules and longer history.
     67No. Without it, WooCommerce's own webhooks already bring your sales and price and stock edits to Koalab within minutes. The plugin makes them arrive in seconds, labels each change with its exact time and cause, records SKU, name, status and weight changes too, and keeps working where WooCommerce's webhooks do not go out.
     68
     69= What happens if I deactivate or delete the plugin? =
     70
     71The plugin tells Koalab, and Koalab subscribes your store to WooCommerce's own webhooks again: your sales keep arriving, within minutes instead of seconds. If you activate it again, Koalab uses it again on its own. If the plugin stops reporting without saying so (for example, it was removed over FTP), Koalab notices when its daily check-in stops arriving and does the same.
     72
     73= Is Koalab Sync free? =
     74
     75There is a free plan, with no credit card. Paid plans add more updates, more channels, scheduled syncs and price rules. See https://koalab.tech for the current plans.
    6476
    6577= Does this plugin slow down my store? =
    6678
    67 No. The plugin only adds an admin page with a connect button. Synchronization runs in Koalab's cloud and talks to your store through the standard WooCommerce REST API, the same one used by mobile apps and integrations.
     79No. Without movement sync the plugin only adds an admin page. With it, the plugin notes which fields changed when a product is saved and sends them once, after the page has been served, plus one small check-in a day. Synchronization runs in Koalab's cloud and talks to your store through the standard WooCommerce REST API.
    6880
    6981= What data does Koalab access? =
    7082
    71 Only your WooCommerce products (names, SKUs, prices, stock), through the API keys you explicitly authorize. Credentials are stored encrypted (AES-256). Koalab never accesses your WordPress admin, customers or orders.
     83Your WooCommerce products (names, SKUs, prices, stock) and your store's status report, through the API keys you explicitly authorize, and — through WooCommerce's webhooks — the status and the lines (products and quantities) of your orders, so a sale reaches your other channels. Nothing about the buyer is kept. Credentials are stored encrypted (AES-256). Koalab never accesses your WordPress admin.
    7284
    7385= Does it work with product variations? =
    7486
    75 Yes — variations are matched and updated just like simple products.
     87Yes — variations are captured and synced just like simple products.
    7688
    77 = Can I undo an update? =
     89= Can I update my catalog from a supplier's price list? =
    7890
    79 Yes. Every update creates a snapshot of the previous values. You can revert the entire update with one click from the history.
    80 
    81 = My supplier's Excel has different column names. Will it work? =
    82 
    83 Yes. Koalab detects common column names automatically (SKU, price, stock, etc. — in English, Spanish and Portuguese) and lets you map any column manually.
     91Yes, in Koalab: upload the Excel or CSV your supplier sends you, map its columns, apply your markup rules, preview every change and revert it with one click. That runs in Koalab, with or without this plugin.
    8492
    8593== Screenshots ==
    … …  
    9199
    92100== Changelog ==
     101
     102= 1.3.0 =
     103* New: while movement sync is connected, the plugin checks in with Koalab once a day and tells it when it is deactivated, deleted or disconnected from its page. Koalab then goes back to WooCommerce's own webhooks instead of waiting for movements that will not come. Up to 1.2.0, a plugin switched off without disconnecting it left Koalab unaware of the store's changes until its next catalog import.
     104* The description now says what the plugin is today: an optional companion of Koalab Sync. Koalab works without it.
     105* New: movement sync reports when a product stops (or starts again) tracking inventory, so Koalab marks it as "no inventory control" instead of reading it as sold out.
     106* Fixed: stock changes made with WooCommerce's bulk edit, when editing an order's items in the admin, or by ERPs and stock plugins that use `wc_update_product_stock()` were not captured.
     107* Fixed: a refund that restocks items now carries its order number, and a refund with several items is no longer reported twice.
     108* Fixed: an order moved back to pending, or a held order whose payment fails (WooCommerce 11.0+), is reported as a return.
     109* Fixed: the retry of a large batch was never scheduled, so those changes were lost if Koalab could not be reached.
     110* The setup text points at Koalab's current screens (Channels → Configure → Plugin tab).
     111* Tested with WordPress 7.1 and WooCommerce 11.2.
     112
     113= 1.2.0 =
     114* Movement sync now also captures SKU, name, status, stock management and weight changes — not just prices and stock.
     115* Fixed: "Open Koalab" and the connect link pointed at a page that no longer exists, so they landed on a 404. Both now open the Channels screen.
     116* Fixed: the setup text sent you to Movements to generate the connection code. It is generated in Channels → your store → Plugin.
     117* Tested with WordPress 7.1.
    93118
    94119= 1.1.0 =
    … …  
    102127== Upgrade Notice ==
    103128
     129= 1.3.0 =
     130Koalab now notices when the plugin is deactivated or stops reporting. Also captures stock changes from bulk edits and other plugins, reports "no inventory control", and no longer loses retries of large batches.
     131
     132= 1.2.0 =
     133Captures more product changes, and fixes the two connection links that led to a missing page.
     134
    104135= 1.1.0 =
    105136Adds optional movement sync (sales, returns and manual edits) plus low-stock alerts.
  • koalab-sync/trunk/uninstall.php

    r3629219 r3762744  
    1212}
    1313
     14// Tell Koalab before the secret goes (1.3.0): the connection ends here, and Koalab goes back to
     15// WooCommerce's own webhooks. Nothing is sent if movement sync was never connected.
     16require_once __DIR__ . '/includes/signals.php';
     17koalab_sync_signal_off( 'uninstalled' );
     18wp_clear_scheduled_hook( KOALAB_SYNC_HEARTBEAT_HOOK );
     19
    1420$koalab_sync_options = array(
    1521        'koalab_sync_show_notice',
    … …  
    2127        'koalab_sync_pair_nonce',
    2228        'koalab_sync_last_error',
     29        'koalab_sync_last_beat',
    2330);
    2431
    … …  
    2633        delete_option( $koalab_sync_option );
    2734}
     35
     36// Batches parked for a retry (1.3.0+), and their scheduled actions.
     37global $wpdb;
     38$koalab_sync_parked = $wpdb->get_col( $wpdb->prepare( "SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE %s", $wpdb->esc_like( 'koalab_sync_retry_' ) . '%' ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery
     39foreach ( $koalab_sync_parked as $koalab_sync_option ) {
     40        delete_option( $koalab_sync_option );
     41}
     42if ( function_exists( 'as_unschedule_all_actions' ) ) {
     43        as_unschedule_all_actions( 'koalab_sync_deliver_parked' );
     44        as_unschedule_all_actions( 'koalab_sync_deliver' );
     45}
Note: See TracChangeset for help on using the changeset viewer.