__( 'Insights Remote DB Streaming', 'ai-eshop-optimizer' ), 'description' => __( 'Stream raw analytics events to your own operator-owned MariaDB / MySQL instance for unbounded BI access. The local store stays small (capped by retention); the remote holds the full event history with no row limit — query freely without slowing the live site.', 'ai-eshop-optimizer' ), 'bullets' => array( __( 'Reuses your existing ERP connection registry — same encrypted credentials, same audit trail.', 'ai-eshop-optimizer' ), __( 'Configurable export frequency (15 min / hourly / daily) with per-table lag dashboard.', 'ai-eshop-optimizer' ), __( 'Local retention failsafe — never purges hot rows until the remote confirms receipt.', 'ai-eshop-optimizer' ), ), ); } public function register_hooks() { add_action( 'wp_ajax_' . self::AJAX_SAVE_SETTINGS, array( $this, 'ajax_save_settings' ) ); add_action( 'wp_ajax_' . self::AJAX_TEST, array( $this, 'ajax_test' ) ); add_action( 'wp_ajax_' . self::AJAX_FORCE_RUN, array( $this, 'ajax_force_run' ) ); add_action( 'wp_ajax_' . self::AJAX_FORCE_REEXPORT, array( $this, 'ajax_force_reexport' ) ); } public function enqueue_admin_assets() { wp_enqueue_style( 'aieo-remote-export', AIEO_URI . 'assets/css/aieo-remote-export.css', array(), AIEO_VERSION ); wp_enqueue_script( 'aieo-remote-export', AIEO_URI . 'assets/js/aieo-remote-export.js', array( 'jquery' ), AIEO_VERSION, true ); wp_localize_script( 'aieo-remote-export', 'aieoRemoteExport', array( 'ajaxUrl' => admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( AIEO_DMM::NONCE_ACTION ), 'actionSave' => self::AJAX_SAVE_SETTINGS, 'actionTest' => self::AJAX_TEST, 'actionForce' => self::AJAX_FORCE_RUN, 'actionReExp' => self::AJAX_FORCE_REEXPORT, 'i18n' => array( 'saved' => __( 'Settings saved.', 'ai-eshop-optimizer' ), 'saveFailed' => __( 'Save failed.', 'ai-eshop-optimizer' ), 'testing' => __( 'Testing…', 'ai-eshop-optimizer' ), 'testPass' => __( 'Test passed — pushed %d rows.', 'ai-eshop-optimizer' ), 'testFail' => __( 'Test failed: %s', 'ai-eshop-optimizer' ), 'running' => __( 'Running export…', 'ai-eshop-optimizer' ), 'runDone' => __( 'Export run completed.', 'ai-eshop-optimizer' ), 'pickTable' => __( 'Pick a table first.', 'ai-eshop-optimizer' ), 'confirmRexp' => __( 'Force re-export from this timestamp? Remote rows will not be deleted (INSERT IGNORE keeps them).', 'ai-eshop-optimizer' ), 'rexpDone' => __( 'Re-export watermark reset.', 'ai-eshop-optimizer' ), ), ) ); } public function render_subtab() { if ( ! current_user_can( 'manage_woocommerce' ) ) { return; } $this->enqueue_admin_assets(); $settings = AIEO_Remote_Export_Manager::get_settings(); $connections = $this->get_connections(); $tables = AIEO_Remote_Export_Manager::exportable_tables(); $dashboard = AIEO_Remote_Export_Manager::dashboard_rows( (int) $settings['connection_id'] ); $this->render_template( 'remote-export.php', array( 'settings' => $settings, 'connections' => $connections, 'tables' => $tables, 'dashboard' => $dashboard, 'nonce' => wp_create_nonce( AIEO_DMM::NONCE_ACTION ), ) ); } public static function dashboard_table_html( $dashboard ) { return AIEO_Table::get_html( array( 'id' => 'aieo-rex-dash', 'page_id' => 'remote_export', 'row_key' => 'slug', 'rows' => $dashboard, 'columns' => array( array( 'key' => 'slug', 'label' => __( 'Table', 'ai-eshop-optimizer' ), 'type' => 'code', 'subline' => 'label', ), array( 'key' => 'local_rows', 'label' => __( 'Local rows', 'ai-eshop-optimizer' ), 'type' => 'integer', ), array( 'key' => 'last_exported_id', 'label' => __( 'Last exported ID', 'ai-eshop-optimizer' ), 'type' => 'id', ), array( 'key' => 'last_exported_ts', 'label' => __( 'Last exported ts', 'ai-eshop-optimizer' ), 'type' => 'datetime', 'format' => array( 'tz' => 'utc' ), ), array( 'key' => 'lag_seconds', 'label' => __( 'Lag', 'ai-eshop-optimizer' ), 'type' => 'duration', 'format' => array( 'format' => 'human' ), 'bands' => array( array( 'min' => DAY_IN_SECONDS, 'tone' => 'danger' ), array( 'min' => HOUR_IN_SECONDS * 6, 'max' => DAY_IN_SECONDS, 'tone' => 'warning' ), ), ), array( 'key' => 'last_status', 'label' => __( 'Last status', 'ai-eshop-optimizer' ), 'type' => 'badge', 'subline' => 'last_error', 'format' => array( 'map' => array( 'ok' => 'success', 'success' => 'success', 'error' => 'danger', 'failed' => 'danger', 'partial' => 'warning', ), ), ), ), 'actions' => array( array( 'id' => 'test', 'label' => __( 'Test', 'ai-eshop-optimizer' ), 'capability' => 'manage_woocommerce', 'variant' => 'secondary', 'hook' => 'aieo-rex-test', 'description' => __( 'Sends one test batch of this table to the remote database and reports what came back.', 'ai-eshop-optimizer' ), ), ), 'empty' => __( 'No tables are selected for export yet.', 'ai-eshop-optimizer' ), ) ); } private function get_connections() { global $wpdb; $table = $wpdb->prefix . 'aieo_erp_connections'; $exists = $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table ) ) === $table; if ( ! $exists ) { return array(); } $rows = $wpdb->get_results( "SELECT id, name, host, db_name FROM {$table} ORDER BY name ASC", ARRAY_A ); return is_array( $rows ) ? $rows : array(); } public function ajax_save_settings() { AIEO_DMM::verify_ajax_request(); if ( ! current_user_can( 'manage_woocommerce' ) ) { wp_send_json_error( array( 'message' => __( 'Insufficient permissions.', 'ai-eshop-optimizer' ) ) ); } $tables = array(); if ( isset( $_POST['tables'] ) && is_array( $_POST['tables'] ) ) { foreach ( wp_unslash( $_POST['tables'] ) as $t ) { $tables[] = sanitize_key( (string) $t ); } } $payload = array( 'connection_id' => isset( $_POST['connection_id'] ) ? (int) $_POST['connection_id'] : 0, 'tables' => $tables, 'frequency' => isset( $_POST['frequency'] ) ? sanitize_key( wp_unslash( (string) $_POST['frequency'] ) ) : 'hourly', 'batch_size' => isset( $_POST['batch_size'] ) ? (int) $_POST['batch_size'] : 1000, 'purge_only_on_success' => ! empty( $_POST['purge_only_on_success'] ), ); $clean = AIEO_Remote_Export_Manager::save_settings( $payload ); if ( class_exists( 'AIEO_Remote_Export_Cron' ) ) { AIEO_Remote_Export_Cron::apply_schedule(); } $dash = AIEO_Remote_Export_Manager::dashboard_rows( (int) $clean['connection_id'] ); wp_send_json_success( array( 'settings' => $clean, 'dashboard' => $dash, 'dashboard_html' => self::dashboard_table_html( $dash ), 'message' => __( 'Settings saved.', 'ai-eshop-optimizer' ), ) ); } public function ajax_test() { AIEO_DMM::verify_ajax_request(); if ( ! current_user_can( 'manage_woocommerce' ) ) { wp_send_json_error( array( 'message' => __( 'Insufficient permissions.', 'ai-eshop-optimizer' ) ) ); } $slug = isset( $_POST['table'] ) ? sanitize_key( wp_unslash( (string) $_POST['table'] ) ) : ''; $allowed = array_keys( AIEO_Remote_Export_Manager::exportable_tables() ); if ( ! in_array( $slug, $allowed, true ) ) { wp_send_json_error( array( 'message' => __( 'Unknown table.', 'ai-eshop-optimizer' ) ) ); } $res = AIEO_Remote_Export_Manager::test_export( $slug, 10 ); if ( empty( $res['ok'] ) ) { wp_send_json_error( array( 'message' => isset( $res['error'] ) ? (string) $res['error'] : __( 'Test failed.', 'ai-eshop-optimizer' ) ) ); } wp_send_json_success( array( 'rows' => (int) $res['rows'], 'message' => isset( $res['error'] ) ? (string) $res['error'] : sprintf( __( 'Test passed — pushed %d rows.', 'ai-eshop-optimizer' ), (int) $res['rows'] ), ) ); } public function ajax_force_run() { AIEO_DMM::verify_ajax_request(); if ( ! current_user_can( 'manage_woocommerce' ) ) { wp_send_json_error( array( 'message' => __( 'Insufficient permissions.', 'ai-eshop-optimizer' ) ) ); } $summary = AIEO_Remote_Export_Cron::run(); $conn_id = (int) AIEO_Remote_Export_Manager::get_settings()['connection_id']; $dash = AIEO_Remote_Export_Manager::dashboard_rows( $conn_id ); wp_send_json_success( array( 'summary' => $summary, 'dashboard' => $dash, 'dashboard_html' => self::dashboard_table_html( $dash ), ) ); } public function ajax_force_reexport() { AIEO_DMM::verify_ajax_request(); if ( ! current_user_can( 'manage_woocommerce' ) ) { wp_send_json_error( array( 'message' => __( 'Insufficient permissions.', 'ai-eshop-optimizer' ) ) ); } $slug = isset( $_POST['table'] ) ? sanitize_key( wp_unslash( (string) $_POST['table'] ) ) : ''; $from_ts = isset( $_POST['from_ts'] ) ? sanitize_text_field( wp_unslash( (string) $_POST['from_ts'] ) ) : ''; $allowed = array_keys( AIEO_Remote_Export_Manager::exportable_tables() ); if ( ! in_array( $slug, $allowed, true ) || $from_ts === '' ) { wp_send_json_error( array( 'message' => __( 'Pick a table and a from-timestamp.', 'ai-eshop-optimizer' ) ) ); } $ts_unix = strtotime( $from_ts ); if ( $ts_unix === false ) { wp_send_json_error( array( 'message' => __( 'Could not parse timestamp.', 'ai-eshop-optimizer' ) ) ); } $normalised = gmdate( 'Y-m-d H:i:s', $ts_unix ); $res = AIEO_Remote_Export_Manager::force_reexport_from( $slug, $normalised ); if ( is_wp_error( $res ) ) { wp_send_json_error( array( 'message' => $res->get_error_message() ) ); } $conn_id = (int) AIEO_Remote_Export_Manager::get_settings()['connection_id']; $dash = AIEO_Remote_Export_Manager::dashboard_rows( $conn_id ); wp_send_json_success( array( 'dashboard' => $dash, 'dashboard_html' => self::dashboard_table_html( $dash ), 'message' => __( 'Re-export watermark reset — next cron tick will re-stream rows after this timestamp.', 'ai-eshop-optimizer' ), ) ); } }