Two bugs fixed: 1. sanitize_text_field() was stripping special characters from Azure AD client secrets (e.g. %XX sequences, angle brackets). Replaced with trim() to preserve the raw secret before encryption. 2. All settings tabs shared one option group (wbc_settings), so saving from any tab would trigger sanitize callbacks for ALL settings. This caused checkboxes on other tabs to reset to 'no' and could interfere with the client secret. Split into per-tab groups: wbc_connection, wbc_sync, wbc_orders. Also clears OAuth token cache when client secret is changed. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
342 lines
10 KiB
PHP
342 lines
10 KiB
PHP
<?php
|
|
/**
|
|
* Admin Settings for WooCommerce Business Central Integration
|
|
*
|
|
* @package WooBusinessCentral
|
|
*/
|
|
|
|
// Prevent direct access
|
|
if ( ! defined( 'ABSPATH' ) ) {
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* Class WBC_Admin
|
|
*
|
|
* Handles admin settings page and AJAX actions.
|
|
*/
|
|
class WBC_Admin {
|
|
|
|
/**
|
|
* Settings page slug
|
|
*/
|
|
const PAGE_SLUG = 'wbc-settings';
|
|
|
|
/**
|
|
* Add admin menu
|
|
*/
|
|
public function add_admin_menu() {
|
|
add_submenu_page(
|
|
'woocommerce',
|
|
__( 'Business Central', 'woo-business-central' ),
|
|
__( 'Business Central', 'woo-business-central' ),
|
|
'manage_woocommerce',
|
|
self::PAGE_SLUG,
|
|
array( $this, 'render_settings_page' )
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Register settings - each tab has its own option group to prevent
|
|
* cross-tab overwrites when saving from a single tab.
|
|
*/
|
|
public function register_settings() {
|
|
// Connection settings (own group)
|
|
register_setting( 'wbc_connection', 'wbc_tenant_id', array(
|
|
'sanitize_callback' => 'sanitize_text_field',
|
|
) );
|
|
register_setting( 'wbc_connection', 'wbc_client_id', array(
|
|
'sanitize_callback' => 'sanitize_text_field',
|
|
) );
|
|
register_setting( 'wbc_connection', 'wbc_client_secret', array(
|
|
'sanitize_callback' => array( $this, 'sanitize_client_secret' ),
|
|
) );
|
|
register_setting( 'wbc_connection', 'wbc_environment', array(
|
|
'sanitize_callback' => 'sanitize_text_field',
|
|
) );
|
|
register_setting( 'wbc_connection', 'wbc_company_id', array(
|
|
'sanitize_callback' => 'sanitize_text_field',
|
|
) );
|
|
|
|
// Sync settings (own group)
|
|
register_setting( 'wbc_sync', 'wbc_sync_frequency', array(
|
|
'sanitize_callback' => array( $this, 'sanitize_frequency' ),
|
|
) );
|
|
register_setting( 'wbc_sync', 'wbc_enable_stock_sync', array(
|
|
'sanitize_callback' => array( $this, 'sanitize_checkbox' ),
|
|
) );
|
|
register_setting( 'wbc_sync', 'wbc_enable_price_sync', array(
|
|
'sanitize_callback' => array( $this, 'sanitize_checkbox' ),
|
|
) );
|
|
|
|
// Order settings (own group)
|
|
register_setting( 'wbc_orders', 'wbc_enable_order_sync', array(
|
|
'sanitize_callback' => array( $this, 'sanitize_checkbox' ),
|
|
) );
|
|
register_setting( 'wbc_orders', 'wbc_default_payment_terms_id', array(
|
|
'sanitize_callback' => 'sanitize_text_field',
|
|
) );
|
|
register_setting( 'wbc_orders', 'wbc_default_shipment_method_id', array(
|
|
'sanitize_callback' => 'sanitize_text_field',
|
|
) );
|
|
register_setting( 'wbc_orders', 'wbc_shipping_item_number', array(
|
|
'sanitize_callback' => 'sanitize_text_field',
|
|
) );
|
|
}
|
|
|
|
/**
|
|
* Sanitize client secret
|
|
*
|
|
* Do NOT use sanitize_text_field() here - it strips characters like %XX
|
|
* sequences, angle brackets, etc. that are common in Azure AD client secrets.
|
|
*
|
|
* @param string $value Input value.
|
|
* @return string Sanitized value.
|
|
*/
|
|
public function sanitize_client_secret( $value ) {
|
|
if ( empty( $value ) ) {
|
|
// Keep existing value if empty (masked field)
|
|
return get_option( 'wbc_client_secret', '' );
|
|
}
|
|
|
|
// If it looks like a masked value, keep existing
|
|
if ( strpos( $value, '***' ) !== false ) {
|
|
return get_option( 'wbc_client_secret', '' );
|
|
}
|
|
|
|
// Only trim whitespace, then encrypt - preserve all secret characters
|
|
$value = trim( wp_unslash( $value ) );
|
|
|
|
// Clear cached OAuth token since secret changed
|
|
WBC_OAuth::clear_token_cache();
|
|
|
|
return WBC_OAuth::encrypt( $value );
|
|
}
|
|
|
|
/**
|
|
* Sanitize frequency
|
|
*
|
|
* @param string $value Input value.
|
|
* @return string Sanitized value.
|
|
*/
|
|
public function sanitize_frequency( $value ) {
|
|
$allowed = array( 'hourly', 'twice_daily', 'daily' );
|
|
$value = sanitize_text_field( $value );
|
|
|
|
if ( ! in_array( $value, $allowed, true ) ) {
|
|
return 'daily';
|
|
}
|
|
|
|
// Reschedule cron if frequency changed
|
|
$old_frequency = get_option( 'wbc_sync_frequency', 'daily' );
|
|
if ( $value !== $old_frequency ) {
|
|
WBC_Cron::reschedule_sync( $value );
|
|
}
|
|
|
|
return $value;
|
|
}
|
|
|
|
/**
|
|
* Sanitize checkbox
|
|
*
|
|
* @param string $value Input value.
|
|
* @return string Sanitized value.
|
|
*/
|
|
public function sanitize_checkbox( $value ) {
|
|
return $value === 'yes' ? 'yes' : 'no';
|
|
}
|
|
|
|
/**
|
|
* Enqueue admin scripts
|
|
*
|
|
* @param string $hook Current admin page hook.
|
|
*/
|
|
public function enqueue_scripts( $hook ) {
|
|
if ( strpos( $hook, self::PAGE_SLUG ) === false ) {
|
|
return;
|
|
}
|
|
|
|
wp_enqueue_style(
|
|
'wbc-admin-style',
|
|
WBC_PLUGIN_URL . 'admin/css/wbc-admin.css',
|
|
array(),
|
|
WBC_VERSION
|
|
);
|
|
|
|
wp_enqueue_script(
|
|
'wbc-admin-script',
|
|
WBC_PLUGIN_URL . 'admin/js/wbc-admin.js',
|
|
array( 'jquery' ),
|
|
WBC_VERSION,
|
|
true
|
|
);
|
|
|
|
wp_localize_script( 'wbc-admin-script', 'wbc_admin', array(
|
|
'ajax_url' => admin_url( 'admin-ajax.php' ),
|
|
'nonce' => wp_create_nonce( 'wbc_admin_nonce' ),
|
|
'strings' => array(
|
|
'testing' => __( 'Testing connection...', 'woo-business-central' ),
|
|
'syncing' => __( 'Syncing products...', 'woo-business-central' ),
|
|
'clearing' => __( 'Clearing logs...', 'woo-business-central' ),
|
|
'loading' => __( 'Loading...', 'woo-business-central' ),
|
|
'confirm_clear' => __( 'Are you sure you want to clear all logs?', 'woo-business-central' ),
|
|
),
|
|
) );
|
|
}
|
|
|
|
/**
|
|
* Add settings link to plugin list
|
|
*
|
|
* @param array $links Existing links.
|
|
* @return array Modified links.
|
|
*/
|
|
public function add_settings_link( $links ) {
|
|
$settings_link = sprintf(
|
|
'<a href="%s">%s</a>',
|
|
admin_url( 'admin.php?page=' . self::PAGE_SLUG ),
|
|
__( 'Settings', 'woo-business-central' )
|
|
);
|
|
|
|
array_unshift( $links, $settings_link );
|
|
|
|
return $links;
|
|
}
|
|
|
|
/**
|
|
* Render settings page
|
|
*/
|
|
public function render_settings_page() {
|
|
// Check user capabilities
|
|
if ( ! current_user_can( 'manage_woocommerce' ) ) {
|
|
wp_die( __( 'You do not have sufficient permissions to access this page.', 'woo-business-central' ) );
|
|
}
|
|
|
|
// Handle tab
|
|
$current_tab = isset( $_GET['tab'] ) ? sanitize_text_field( wp_unslash( $_GET['tab'] ) ) : 'connection';
|
|
|
|
include WBC_PLUGIN_DIR . 'admin/partials/wbc-admin-display.php';
|
|
}
|
|
|
|
/**
|
|
* AJAX: Test connection
|
|
*/
|
|
public function ajax_test_connection() {
|
|
check_ajax_referer( 'wbc_admin_nonce', 'nonce' );
|
|
|
|
if ( ! current_user_can( 'manage_woocommerce' ) ) {
|
|
wp_send_json_error( array( 'message' => __( 'Permission denied.', 'woo-business-central' ) ) );
|
|
}
|
|
|
|
$result = WBC_OAuth::test_connection();
|
|
|
|
if ( is_wp_error( $result ) ) {
|
|
wp_send_json_error( array( 'message' => $result->get_error_message() ) );
|
|
}
|
|
|
|
wp_send_json_success( $result );
|
|
}
|
|
|
|
/**
|
|
* AJAX: Manual sync
|
|
*/
|
|
public function ajax_manual_sync() {
|
|
check_ajax_referer( 'wbc_admin_nonce', 'nonce' );
|
|
|
|
if ( ! current_user_can( 'manage_woocommerce' ) ) {
|
|
wp_send_json_error( array( 'message' => __( 'Permission denied.', 'woo-business-central' ) ) );
|
|
}
|
|
|
|
$result = WBC_Cron::run_sync_now();
|
|
|
|
if ( isset( $result['success'] ) && $result['success'] ) {
|
|
wp_send_json_success( $result );
|
|
} else {
|
|
wp_send_json_error( $result );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* AJAX: Clear logs
|
|
*/
|
|
public function ajax_clear_logs() {
|
|
check_ajax_referer( 'wbc_admin_nonce', 'nonce' );
|
|
|
|
if ( ! current_user_can( 'manage_woocommerce' ) ) {
|
|
wp_send_json_error( array( 'message' => __( 'Permission denied.', 'woo-business-central' ) ) );
|
|
}
|
|
|
|
WBC_Logger::clear_logs();
|
|
|
|
wp_send_json_success( array( 'message' => __( 'Logs cleared successfully.', 'woo-business-central' ) ) );
|
|
}
|
|
|
|
/**
|
|
* AJAX: Get companies
|
|
*/
|
|
public function ajax_get_companies() {
|
|
check_ajax_referer( 'wbc_admin_nonce', 'nonce' );
|
|
|
|
if ( ! current_user_can( 'manage_woocommerce' ) ) {
|
|
wp_send_json_error( array( 'message' => __( 'Permission denied.', 'woo-business-central' ) ) );
|
|
}
|
|
|
|
$result = WBC_API_Client::get_companies();
|
|
|
|
if ( is_wp_error( $result ) ) {
|
|
wp_send_json_error( array( 'message' => $result->get_error_message() ) );
|
|
}
|
|
|
|
$companies = isset( $result['value'] ) ? $result['value'] : array();
|
|
|
|
wp_send_json_success( array( 'companies' => $companies ) );
|
|
}
|
|
|
|
/**
|
|
* Handle CSV export of logs
|
|
*/
|
|
public function handle_csv_export() {
|
|
if ( ! isset( $_GET['wbc_export_logs'] ) || $_GET['wbc_export_logs'] !== '1' ) {
|
|
return;
|
|
}
|
|
|
|
if ( ! isset( $_GET['_wpnonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) ), 'wbc_export_logs' ) ) {
|
|
wp_die( esc_html__( 'Security check failed.', 'woo-business-central' ) );
|
|
}
|
|
|
|
if ( ! current_user_can( 'manage_woocommerce' ) ) {
|
|
wp_die( esc_html__( 'Permission denied.', 'woo-business-central' ) );
|
|
}
|
|
|
|
$csv = WBC_Logger::export_to_csv();
|
|
|
|
header( 'Content-Type: text/csv; charset=utf-8' );
|
|
header( 'Content-Disposition: attachment; filename=wbc-logs-' . gmdate( 'Y-m-d' ) . '.csv' );
|
|
header( 'Pragma: no-cache' );
|
|
header( 'Expires: 0' );
|
|
|
|
echo $csv; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- CSV output
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* Get logs for display
|
|
*
|
|
* @param array $args Query arguments.
|
|
* @return array Logs data.
|
|
*/
|
|
public static function get_logs_for_display( $args = array() ) {
|
|
$defaults = array(
|
|
'limit' => 50,
|
|
'offset' => 0,
|
|
'level' => '',
|
|
);
|
|
|
|
$args = wp_parse_args( $args, $defaults );
|
|
|
|
return array(
|
|
'logs' => WBC_Logger::get_logs( $args ),
|
|
'total' => WBC_Logger::get_log_count( $args ),
|
|
);
|
|
}
|
|
}
|