ArcWOC/admin/services/class-hesabix-v2-connection-service.php
Babak Alizadeh 11e8f522d3 Release 4.10.0: safe disconnect and change-business cleanup
Add a dedicated disconnect flow that truncates mappings/queue, clears
business-scoped IDs (shipping/fee/warehouse/bank), bridge token, sync
meta, and best-effort Hesabix Woo bridge settings. Change-business and
setup-complete now require this cleanup so reconnecting to a new
business cannot reuse stale links. Also expose delete-data-on-uninstall
in settings.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-22 16:03:06 +00:00

449 lines
13 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
/**
* قطع اتصال امن از حسابیکس و پاکسازی لینک‌ها / شناسه‌های وابسته به کسب‌وکار.
*
* @package Hesabix_V2
* @subpackage Hesabix_V2/admin/services
*/
if (!defined('WPINC')) {
die;
}
class Hesabix_V2_Connection_Service
{
/**
* متاهای سفارش/پست که باید هنگام قطع اتصال پاک شوند (نه فیلدهای checkout مشتری).
*
* @return string[]
*/
public static function sync_meta_keys_to_clear()
{
$keys = array(
'_hesabix_v2_pause_auto_sync',
'_hesabix_v2_invoice_rp_sync_fp',
'_hesabix_v2_order_system_note_id',
'_hesabix_v2_invoice_sync_error_note_id',
'_hesabix_v2_last_pushed_stock_qty',
);
if (class_exists('Hesabix_V2_Invoice_Profit_Service', false)) {
$keys = array_merge(
$keys,
array(
Hesabix_V2_Invoice_Profit_Service::META_PROFIT,
Hesabix_V2_Invoice_Profit_Service::META_PROFIT_PERCENT,
Hesabix_V2_Invoice_Profit_Service::META_GROSS,
Hesabix_V2_Invoice_Profit_Service::META_NET,
Hesabix_V2_Invoice_Profit_Service::META_AT,
Hesabix_V2_Invoice_Profit_Service::META_INVOICE_ID,
Hesabix_V2_Invoice_Profit_Service::META_TOTAL_COST,
Hesabix_V2_Invoice_Profit_Service::META_TOTAL_SALES,
Hesabix_V2_Invoice_Profit_Service::META_CURRENCY_CODE,
Hesabix_V2_Invoice_Profit_Service::META_CURRENCY_TITLE,
)
);
}
if (class_exists('Hesabix_V2_Stock_Push_Service', false)) {
$keys[] = Hesabix_V2_Stock_Push_Service::META_LAST_PUSHED_QTY;
}
return array_values(array_unique($keys));
}
/**
* آیا اتصال فعالی (کلید یا شناسه کسب‌وکار) ذخیره شده است؟
*
* @return bool
*/
public static function is_connected()
{
$key = (string) get_option('hesabix_v2_api_key', '');
$bid = (int) get_option('hesabix_v2_business_id', 0);
return ($key !== '' || $bid > 0);
}
/**
* قطع اتصال کامل.
*
* @param array $args {
* @type bool $clear_remote_bridge پاک کردن URL/توکن پل در کسب‌وکار قبلی حسابیکس (بهترین تلاش)
* @type bool $show_setup_wizard نمایش مجدد ویزارد
* @type bool $truncate_logs خالی کردن جدول/فایل لاگ همگام‌سازی
* @type bool $clear_order_meta پاک کردن متای همگام‌سازی سفارش/محصول
* }
* @return array{success:bool,message:string,details:array}
*/
public static function disconnect(array $args = array())
{
$args = wp_parse_args(
$args,
array(
'clear_remote_bridge' => true,
'show_setup_wizard' => true,
'truncate_logs' => true,
'clear_order_meta' => true,
)
);
$details = array(
'previous_business_id' => (int) get_option('hesabix_v2_business_id', 0),
'remote_bridge_cleared' => false,
'remote_bridge_error' => '',
'mappings_truncated' => false,
'queue_truncated' => false,
'meta_rows_deleted' => 0,
'crons_cleared' => false,
);
$old_bid = $details['previous_business_id'];
$had_key = (string) get_option('hesabix_v2_api_key', '') !== '';
if (!empty($args['clear_remote_bridge']) && $old_bid > 0 && $had_key) {
$api = new Hesabix_V2_Api();
$remote = $api->clear_woocommerce_bridge_settings($old_bid);
if (!empty($remote['success'])) {
$details['remote_bridge_cleared'] = true;
} else {
$details['remote_bridge_error'] = isset($remote['message'])
? (string) $remote['message']
: __('پاکسازی تنظیمات پل در حسابیکس ناموفق بود.', 'hesabix-v2');
}
}
self::clear_cron_hooks();
$details['crons_cleared'] = true;
$details['queue_truncated'] = self::truncate_queue_table();
$details['mappings_truncated'] = self::truncate_mappings_table();
if (!empty($args['truncate_logs']) && class_exists('Hesabix_V2_Log_Service', false)) {
Hesabix_V2_Log_Service::clear_all_logs();
$details['logs_truncated'] = true;
}
if (!empty($args['clear_order_meta'])) {
$details['meta_rows_deleted'] = self::clear_sync_post_meta();
}
self::clear_connection_options();
self::scrub_business_scoped_preferences();
self::clear_bridge_local();
self::clear_plugin_transients();
// افزونه هنوز فعال است؛ فقط cronهای پایه را برگردان (کشش موجودی خاموش می‌ماند).
self::reschedule_core_crons();
if (class_exists('Hesabix_V2_Currency_Service', false)) {
Hesabix_V2_Currency_Service::invalidate_list_cache();
}
if (class_exists('Hesabix_V2_Order_Fiscal_Service', false)) {
Hesabix_V2_Order_Fiscal_Service::invalidate_bounds_cache();
}
update_option('hesabix_v2_enabled', false);
update_option('hesabix_v2_setup_completed', false);
if (!empty($args['show_setup_wizard'])) {
set_transient('hesabix_v2_show_setup_wizard', true, HOUR_IN_SECONDS);
} else {
delete_transient('hesabix_v2_show_setup_wizard');
}
if (class_exists('Hesabix_V2_Log_Service', false)) {
Hesabix_V2_Log_Service::info(
__('قطع اتصال از حسابیکس انجام شد.', 'hesabix-v2'),
array(
'entity_type' => 'connection',
'details' => $details,
)
);
}
$message = __('اتصال قطع شد و لینک‌های کسب‌وکار قبلی پاک شدند.', 'hesabix-v2');
if ($details['remote_bridge_error'] !== '') {
$message .= ' ' . sprintf(
/* translators: %s: remote API error */
__('پاکسازی پل در حسابیکس: %s (در صورت نیاز در پنل حسابیکس تنظیمات ووکامرس کسب‌وکار قبلی را خالی کنید.)', 'hesabix-v2'),
$details['remote_bridge_error']
);
}
return array(
'success' => true,
'message' => $message,
'details' => $details,
);
}
/**
* قبل از ذخیرهٔ اتصال جدید: اگر قبلاً متصل بود، قطع اتصال امن.
*
* @param int $new_business_id
* @return array|null نتیجه disconnect یا null اگر نیازی نبود
*/
public static function disconnect_before_new_connection($new_business_id = 0)
{
if (!self::is_connected() && !self::has_mapping_or_queue_rows()) {
return null;
}
$old_bid = (int) get_option('hesabix_v2_business_id', 0);
$new_bid = (int) $new_business_id;
return self::disconnect(
array(
'clear_remote_bridge' => ($old_bid > 0 && ($new_bid < 1 || $new_bid !== $old_bid)),
'show_setup_wizard' => false,
'truncate_logs' => true,
'clear_order_meta' => true,
)
);
}
/**
* @return bool
*/
public static function has_mapping_or_queue_rows()
{
global $wpdb;
$map = $wpdb->prefix . 'hesabix_v2';
$queue = $wpdb->prefix . 'hesabix_v2_queue';
$map_n = (int) $wpdb->get_var("SELECT COUNT(1) FROM {$map}"); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$queue_n = (int) $wpdb->get_var("SELECT COUNT(1) FROM {$queue}"); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
return ($map_n > 0 || $queue_n > 0);
}
/**
* @return void
*/
private static function clear_cron_hooks()
{
wp_clear_scheduled_hook('hesabix_v2_process_queue');
wp_clear_scheduled_hook('hesabix_v2_clean_old_logs');
wp_clear_scheduled_hook('hesabix_v2_pull_stock_cron');
}
/**
* @return void
*/
private static function reschedule_core_crons()
{
if (!wp_next_scheduled('hesabix_v2_process_queue')) {
wp_schedule_event(time(), 'every_5_minutes', 'hesabix_v2_process_queue');
}
if (!wp_next_scheduled('hesabix_v2_clean_old_logs')) {
wp_schedule_event(time(), 'daily', 'hesabix_v2_clean_old_logs');
}
}
/**
* @return bool
*/
private static function truncate_mappings_table()
{
global $wpdb;
$table = $wpdb->prefix . 'hesabix_v2';
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
return false !== $wpdb->query("TRUNCATE TABLE {$table}");
}
/**
* @return bool
*/
private static function truncate_queue_table()
{
global $wpdb;
$table = $wpdb->prefix . 'hesabix_v2_queue';
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
return false !== $wpdb->query("TRUNCATE TABLE {$table}");
}
/**
* پاک کردن متاهای همگام‌سازی از postmeta و در صورت HPOS از wc_orders_meta.
*
* @return int تعداد تقریبی ردیف‌های حذف‌شده
*/
private static function clear_sync_post_meta()
{
global $wpdb;
$keys = self::sync_meta_keys_to_clear();
if (empty($keys)) {
return 0;
}
$deleted = 0;
$placeholders = implode(',', array_fill(0, count($keys), '%s'));
// phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
$sql = $wpdb->prepare(
"DELETE FROM {$wpdb->postmeta} WHERE meta_key IN ($placeholders)",
...$keys
);
if ($sql) {
$r = $wpdb->query($sql);
if (is_numeric($r)) {
$deleted += (int) $r;
}
}
$orders_meta = $wpdb->prefix . 'wc_orders_meta';
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$table_exists = $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $orders_meta));
if ($table_exists === $orders_meta) {
// phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
$sql2 = $wpdb->prepare(
"DELETE FROM {$orders_meta} WHERE meta_key IN ($placeholders)",
...$keys
);
if ($sql2) {
$r2 = $wpdb->query($sql2);
if (is_numeric($r2)) {
$deleted += (int) $r2;
}
}
}
return $deleted;
}
/**
* گزینه‌های هویت اتصال.
*
* @return void
*/
private static function clear_connection_options()
{
$delete = array(
'hesabix_v2_api_key',
'hesabix_v2_business_id',
'hesabix_v2_fiscal_year_id',
'hesabix_v2_default_warehouse_id',
'hesabix_v2_default_bank_id',
'hesabix_v2_default_cash_register_id',
'hesabix_v2_currency_id',
'hesabix_v2_shipping_product_id',
'hesabix_v2_fee_product_id',
'hesabix_v2_orphan_parent_products',
'hesabix_v2_stock_pull_last',
'hesabix_v2_marketplace_woo_status',
);
foreach ($delete as $opt) {
delete_option($opt);
}
update_option('hesabix_v2_opening_inventory_completed', false);
}
/**
* پاک کردن شناسه‌های وابسته به کسب‌وکار داخل تنظیمات نگهداشت‌شونده.
*
* @return void
*/
private static function scrub_business_scoped_preferences()
{
$sync = get_option('hesabix_v2_sync_settings', array());
if (is_array($sync)) {
$sync['invoice_extra_tag_ids'] = '';
$sync['shipping_adjustment_account_id'] = 0;
$sync['fee_adjustment_account_id'] = 0;
$sync['fee_deduction_account_id'] = 0;
update_option('hesabix_v2_sync_settings', $sync);
}
$stock_pull = get_option('hesabix_v2_stock_pull', array());
if (!is_array($stock_pull)) {
$stock_pull = array();
}
$stock_pull['enabled'] = false;
$stock_pull['warehouse_ids'] = array();
$stock_pull['warehouse_scope'] = isset($stock_pull['warehouse_scope']) ? $stock_pull['warehouse_scope'] : 'default';
update_option('hesabix_v2_stock_pull', $stock_pull);
$ob = get_option('hesabix_v2_opening_inventory_prefs', array());
if (!is_array($ob)) {
$ob = array();
}
$ob['inventory_account_id'] = 0;
$ob['equity_account_id'] = 0;
$ob['warehouse_override'] = 0;
update_option('hesabix_v2_opening_inventory_prefs', $ob);
$inv_key = class_exists('Hesabix_V2_Invoice_Warehouse_Service', false)
? Hesabix_V2_Invoice_Warehouse_Service::OPTION_KEY
: 'hesabix_v2_invoice_warehouse_rules';
update_option(
$inv_key,
array(
'resolution' => 'default',
'rules' => array(),
)
);
}
/**
* @return void
*/
private static function clear_bridge_local()
{
if (class_exists('Hesabix_V2_Bridge_Rest', false)) {
Hesabix_V2_Bridge_Rest::clear_token();
update_option(Hesabix_V2_Bridge_Rest::OPT_ENABLED, false);
} else {
delete_option('hesabix_v2_bridge_token_hash');
update_option('hesabix_v2_bridge_enabled', false);
}
}
/**
* پاک کردن transientهای عملیاتی افزونه (به‌جز کش به‌روزرسانی مخزن).
*
* @return void
*/
private static function clear_plugin_transients()
{
global $wpdb;
$keep_like = array(
'_transient_hesabix_v2_remote_version',
'_transient_timeout_hesabix_v2_remote_version',
'_transient_hesabix_v2_upd_',
'_transient_timeout_hesabix_v2_upd_',
);
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
$rows = $wpdb->get_col(
"SELECT option_name FROM {$wpdb->options}
WHERE option_name LIKE '_transient_hesabix_v2_%'
OR option_name LIKE '_transient_timeout_hesabix_v2_%'"
);
if (!is_array($rows)) {
return;
}
foreach ($rows as $name) {
$name = (string) $name;
$skip = false;
foreach ($keep_like as $prefix) {
if (strpos($name, $prefix) === 0) {
$skip = true;
break;
}
}
if ($skip) {
continue;
}
if (strpos($name, '_transient_timeout_') === 0) {
$key = substr($name, strlen('_transient_timeout_'));
delete_transient($key);
} elseif (strpos($name, '_transient_') === 0) {
$key = substr($name, strlen('_transient_'));
delete_transient($key);
}
}
}
}